/*
 * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The OpenAirInterface Software Alliance licenses this file to You under
 * the OAI Public License, Version 1.1  (the "License"); you may not use this file
 * except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.openairinterface.org/?page_id=698
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *-------------------------------------------------------------------------------
 * For more information about the OpenAirInterface (OAI) Software Alliance:
 *      contact@openairinterface.org
 */

/*! \file gNB_scheduler_primitives.c
 * \brief primitives used by gNB for BCH, RACH, ULSCH, DLSCH scheduling
 * \author  Raymond Knopp, Guy De Souza
 * \date 2018, 2019
 * \email: knopp@eurecom.fr, desouza@eurecom.fr
 * \version 1.0
 * \company Eurecom
 * @ingroup _mac
 */

#include <softmodem-common.h>
#include "assertions.h"

#include "NR_MAC_gNB/nr_mac_gNB.h"
#include "NR_MAC_gNB/mac_proto.h"
#include "common/utils/LOG/log.h"
#include "common/utils/LOG/vcd_signal_dumper.h"
#include "common/utils/nr/nr_common.h"
#include "UTIL/OPT/opt.h"

#include "openair2/LAYER2/nr_rlc/nr_rlc_oai_api.h"
#include "F1AP_CauseRadioNetwork.h"

#include "intertask_interface.h"
#include "openair2/F1AP/f1ap_ids.h"
#include "F1AP_CauseRadioNetwork.h"

#include "T.h"

#include "uper_encoder.h"
#include "uper_decoder.h"

#include "SIMULATION/TOOLS/sim.h" // for taus

#define ENABLE_MAC_PAYLOAD_DEBUG
#define DEBUG_gNB_SCHEDULER 1

#include "common/ran_context.h"
#include "nfapi/oai_integration/vendor_ext.h"

#include "common/utils/alg/find.h"

// 3GPP TS 38.331 Section 12 Table 12.1-1: UE performance requirements for RRC procedures for UEs
#define NR_RRC_SETUP_DELAY_MS           10
#define NR_RRC_RECONFIGURATION_DELAY_MS 10
#define NR_RRC_BWP_SWITCHING_DELAY_MS   6

// #define DEBUG_DCI
//  CQI TABLES (10 times the value in 214 to adequately compare with R)
//  Table 1 (38.214 5.2.2.1-2)
static const uint16_t cqi_table1[16][2] = {{0, 0},
                                           {2, 780},
                                           {2, 1200},
                                           {2, 1930},
                                           {2, 3080},
                                           {2, 4490},
                                           {2, 6020},
                                           {4, 3780},
                                           {4, 4900},
                                           {4, 6160},
                                           {6, 4660},
                                           {6, 5670},
                                           {6, 6660},
                                           {6, 7720},
                                           {6, 8730},
                                           {6, 9480}};

// Table 2 (38.214 5.2.2.1-3)
static const uint16_t cqi_table2[16][2] = {{0, 0},
                                           {2, 780},
                                           {2, 1930},
                                           {2, 4490},
                                           {4, 3780},
                                           {4, 4900},
                                           {4, 6160},
                                           {6, 4660},
                                           {6, 5670},
                                           {6, 6660},
                                           {6, 7720},
                                           {6, 8730},
                                           {8, 7110},
                                           {8, 7970},
                                           {8, 8850},
                                           {8, 9480}};

// Table 2 (38.214 5.2.2.1-4)
static const uint16_t cqi_table3[16][2] = {{0, 0},
                                           {2, 300},
                                           {2, 500},
                                           {2, 780},
                                           {2, 1200},
                                           {2, 1930},
                                           {2, 3080},
                                           {2, 4490},
                                           {2, 6020},
                                           {4, 3780},
                                           {4, 4900},
                                           {4, 6160},
                                           {6, 4660},
                                           {6, 5670},
                                           {6, 6660},
                                           {6, 7720}};

static void determine_aggregation_level_search_order(int agg_level_search_order[NUM_PDCCH_AGG_LEVELS],
                                                     float pdcch_cl_adjust);

uint8_t get_dl_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format)
{
  // TODO check this but it should be enough for now
  // if there is not csi report activated RI is 0 from initialization
  if(dci_format == NR_DL_DCI_FORMAT_1_0)
    return 1;
  else
    return sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1;
}

int get_ul_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format)
{
  if(dci_format == NR_UL_DCI_FORMAT_0_0)
    return 1;
  else
    return sched_ctrl->srs_feedback.ul_ri + 1;
}

// Table 5.2.2.2.1-3 and Table 5.2.2.2.1-4 in 38.214
void get_k1_k2_indices(const int layers, const int N1, const int N2, const int i13, int *k1, int *k2)
{
  AssertFatal(layers > 0 && layers < 5, "Number of layers %d not supported\n", layers);
  *k1 = 0;
  *k2 = 0;
  if (layers == 2) {
    if (N2 == 1)
      *k1 = i13;
    else if (N1 == N2) {
      *k1 = i13 & 1;
      *k2 = i13 >> 1;
    }
    else {
      *k1 = (i13 & 1) + (i13 == 3);
      *k2 = (i13 == 2);
    }
  }
  if (layers == 3 || layers == 4) {
    if (N2 == 1)
      *k1 = i13 + 1;
    else if (N1 == 2 && N2 == 2) {
      *k1 = !(i13 & 1);
      *k2 = (i13 > 0);
    }
    else {
      if (i13 == 0)
        *k1 = 1;
      if (i13 == 1)
        *k2 = 1;
      if (i13 == 2) {
        *k1 = 1;
        *k2 = 1;
      }
      if (i13 == 3)
        *k1 = 2;
    }
  }
}

uint16_t get_pm_index(const gNB_MAC_INST *nrmac,
                      const NR_UE_info_t *UE,
                      nr_dci_format_t dci_format,
                      int layers,
                      int xp_pdsch_antenna_ports)
{
  if (dci_format == NR_DL_DCI_FORMAT_1_0 || nrmac->identity_pm || xp_pdsch_antenna_ports == 1)
    return 0; //identity matrix (basic 5G configuration handled by PMI report is with XP antennas)
  const NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
  const int report_id = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.csi_report_id;
  const nr_csi_report_t *csi_report = &UE->csi_report_template[report_id];
  const int N1 = csi_report->N1;
  const int N2 = csi_report->N2;
  const int antenna_ports = (N1 * N2) << 1;
  if (antenna_ports < 2)
    return 0; // single antenna port

  int x1 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1;
  const int x2 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2;
  LOG_D(NR_MAC,"PMI report: x1 %d x2 %d layers: %d\n", x1, x2, layers);

  int prev_layers_size = 0;
  for (int i = 1; i < layers; i++)
    prev_layers_size += nrmac->precoding_matrix_size[i - 1];

  // need to return PM index to matrix initialized in init_DL_MIMO_codebook
  // index 0 is for identity matrix
  // order of matrices depends on layers to be transmitted
  // elements from 1 to n for 1 layer
  // elements from n+1 to m for 2 layers etc.
  if (antenna_ports == 2)
    return 1 + prev_layers_size + x2;  // 0 for identity matrix
  else {
    int idx = layers - 1;
    // the order of i1x in X1 report needs to be verified
    // the standard is not very clear (Table 6.3.1.1.2-7 in 38.212)
    // it says: PMI wideband information fields X1 , from left to right
    int bitlen = csi_report->csi_meas_bitlen.pmi_i13_bitlen[idx];
    if (layers == 1 && bitlen != 0) {
      LOG_E(NR_MAC, "Invalid i13 bit length %d for single layer! It should be 0\n", bitlen);
      return 0;
    }
    const int i13 = x1 & ((1 << bitlen) - 1);
    x1 >>= bitlen;
    bitlen = csi_report->csi_meas_bitlen.pmi_i12_bitlen[idx];
    const int i12 = x1 & ((1 << bitlen) - 1);
    x1 >>= bitlen;
    bitlen = csi_report->csi_meas_bitlen.pmi_i11_bitlen[idx];
    const int i11 = x1 & ((1 << bitlen) - 1);
    const int i2 = x2;
    int k1, k2;
    get_k1_k2_indices(layers, N1, N2, i13, &k1, &k2); // get indices k1 and k2 for PHY matrix (not actual k1 and k2 values)
    const int O2 = N2 == 1 ? 1 : 4;
    int O1 = 4; //Horizontal beam oversampling = 4 for more than 2 antenna ports
    int max_i2 = 0;
    int lay_index = 0;
    max_i2 = layers == 1 ? 4 : 2;
    int K1, K2;
    get_K1_K2(N1, N2, &K1, &K2, layers);
    // computing precoding matrix index according to rule set in allocation function init_codebook_gNB
    lay_index = i2 + (i11 * max_i2) + (i12 * max_i2 * N1 * O1) + (k1 * max_i2 * N1 * O1 * N2 * O2) + (k2 * max_i2 * N1 * O1 * N2 * O2 * K1);
    return 1 + prev_layers_size + lay_index;
  }
}

uint8_t get_mcs_from_cqi(int mcs_table, int cqi_table, int cqi_idx)
{
  if (cqi_idx <= 0) {
    LOG_E(NR_MAC, "invalid cqi_idx %d, default to MCS 9\n", cqi_idx);
    return 9;
  }

  if (mcs_table != cqi_table) {
    LOG_E(NR_MAC, "indices of CQI (%d) and MCS (%d) tables don't correspond yet\n", cqi_table, mcs_table);
    return 9;
  }

  uint16_t target_coderate, target_qm;
  switch (cqi_table) {
    case 0:
      target_qm = cqi_table1[cqi_idx][0];
      target_coderate = cqi_table1[cqi_idx][1];
      break;
    case 1:
      target_qm = cqi_table2[cqi_idx][0];
      target_coderate = cqi_table2[cqi_idx][1];
      break;
    case 2:
      target_qm = cqi_table3[cqi_idx][0];
      target_coderate = cqi_table3[cqi_idx][1];
      break;
    default:
      AssertFatal(1==0,"Invalid cqi table index %d\n",cqi_table);
  }
  const int max_mcs = mcs_table == 1 ? 27 : 28;
  for (int i = 0; i <= max_mcs; i++) {
    const int R = nr_get_code_rate_dl(i, mcs_table);
    const int Qm = nr_get_Qm_dl(i, mcs_table);
    if (Qm == target_qm && target_coderate <= R)
      return i;
  }

  LOG_E(NR_MAC, "could not find maximum MCS from cqi_idx %d, default to 9\n", cqi_idx);
  return 9;
}

NR_pdsch_dmrs_t get_dl_dmrs_params(const NR_ServingCellConfigCommon_t *scc,
                                   const NR_UE_DL_BWP_t *dl_bwp,
                                   const NR_tda_info_t *tda_info,
                                   const int Layers)
{
  NR_pdsch_dmrs_t dmrs = {0};
  int frontloaded_symb = 1; // default value
  nr_dci_format_t dci_format = dl_bwp ? dl_bwp->dci_format : NR_DL_DCI_FORMAT_1_0;
  if (dci_format == NR_DL_DCI_FORMAT_1_0) {
    dmrs.numDmrsCdmGrpsNoData = tda_info->nrOfSymbols <= 2 ? 1 : 2;
    dmrs.dmrs_ports_id = 0;
  } else {
    //TODO first basic implementation of dmrs port selection
    //     only vaild for a single codeword
    //     for now it assumes a selection of Nl consecutive dmrs ports
    //     and a single front loaded symbol
    //     dmrs_ports_id is the index of Tables 7.3.1.2.2-1/2/3/4
    //     number of front loaded symbols need to be consistent with maxLength
    //     when a more complete implementation is done

    switch (Layers) {
      case 1:
#ifdef  ENABLE_AERIAL
        dmrs.dmrs_ports_id = 3;
        dmrs.numDmrsCdmGrpsNoData = 2;
#else
        dmrs.dmrs_ports_id = 0;
        dmrs.numDmrsCdmGrpsNoData = 1;
#endif
        frontloaded_symb = 1;
        break;
      case 2:
#ifdef  ENABLE_AERIAL
        dmrs.dmrs_ports_id = 7;
        dmrs.numDmrsCdmGrpsNoData = 2;
#else
        dmrs.dmrs_ports_id = 2;
        dmrs.numDmrsCdmGrpsNoData = 1;
#endif
        frontloaded_symb = 1;
        break;
      case 3:
        dmrs.dmrs_ports_id = 9;
        dmrs.numDmrsCdmGrpsNoData = 2;
        frontloaded_symb = 1;
        break;
      case 4:
        dmrs.dmrs_ports_id = 10;
        dmrs.numDmrsCdmGrpsNoData = 2;
        frontloaded_symb = 1;
        break;
      default:
        AssertFatal(1==0,"Number of layers %d\n not supported or not valid\n",Layers);
    }
  }

  dmrs.n_scid = 0;
  long *scramblingID = NULL;
  NR_PDSCH_Config_t *pdsch_Config = dl_bwp ? dl_bwp->pdsch_Config : NULL;
  if (pdsch_Config) {
    NR_DMRS_DownlinkConfig_t *dmrs_Config = tda_info->mapping_type == typeB ?
                                            pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeB->choice.setup :
                                            pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup;
    dmrs.phaseTrackingRS = dmrs_Config->phaseTrackingRS ? dmrs_Config->phaseTrackingRS->choice.setup : NULL;
    dmrs.dmrsConfigType = dmrs_Config->dmrs_Type != NULL;
    scramblingID = dmrs.n_scid ? dmrs_Config->scramblingID1 : dmrs_Config->scramblingID0;
  } else {
    dmrs.dmrsConfigType = NFAPI_NR_DMRS_TYPE1;
    dmrs.phaseTrackingRS = NULL;
  }
  dmrs.scrambling_id = scramblingID ? *scramblingID : *scc->physCellId;
  dmrs.N_PRB_DMRS = dmrs.numDmrsCdmGrpsNoData * (dmrs.dmrsConfigType == NFAPI_NR_DMRS_TYPE1 ? 6 : 4);
  dmrs.dl_dmrs_symb_pos = fill_dmrs_mask(pdsch_Config, dci_format, scc->dmrs_TypeA_Position, tda_info->nrOfSymbols, tda_info->startSymbolIndex, tda_info->mapping_type, frontloaded_symb);
  dmrs.N_DMRS_SLOT = get_num_dmrs(dmrs.dl_dmrs_symb_pos);
  LOG_D(NR_MAC,"Filling dmrs info, ps->N_PRB_DMRS %d, ps->dl_dmrs_symb_pos %x, ps->N_DMRS_SLOT %d\n",dmrs.N_PRB_DMRS,dmrs.dl_dmrs_symb_pos,dmrs.N_DMRS_SLOT);
  return dmrs;
}

NR_ControlResourceSet_t *get_coreset(gNB_MAC_INST *nrmac,
                                     NR_ServingCellConfigCommon_t *scc,
                                     NR_BWP_DownlinkDedicated_t *bwp_dedicated,
                                     NR_ControlResourceSetId_t coreset_id)
{
  if (coreset_id == 0) {
    return nrmac->sched_ctrlCommon->coreset; // this is coreset 0
  }
  if (bwp_dedicated) {
    const int n = bwp_dedicated->pdcch_Config->choice.setup->controlResourceSetToAddModList->list.count;
    for (int i = 0; i < n; i++) {
      NR_ControlResourceSet_t *coreset = bwp_dedicated->pdcch_Config->choice.setup->controlResourceSetToAddModList->list.array[i];
      if (coreset->controlResourceSetId == coreset_id) {
        return coreset;
      }
    }
    AssertFatal(false, "Couldn't find CORESET with id %ld in BWP_DownlinkDedicated\n", coreset_id);
  }
  if (scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->commonControlResourceSet) {
    NR_ControlResourceSet_t *coreset =
        scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->commonControlResourceSet;
    if (coreset->controlResourceSetId == coreset_id) {
      return coreset;
    }
    AssertFatal(false, "Couldn't find CORESET with id %ld in commonControlResourceSet\n", coreset_id);
  }
  AssertFatal(false, "Couldn't find coreset with id %ld\n", coreset_id);
}

static NR_SearchSpace_t *get_searchspace(NR_ServingCellConfigCommon_t *scc,
                                         NR_BWP_DownlinkDedicated_t *bwp_dedicated,
                                         NR_SearchSpace__searchSpaceType_PR target_ss)
{
  if (bwp_dedicated) {
    const int n = bwp_dedicated->pdcch_Config->choice.setup->searchSpacesToAddModList->list.count;
    for (int i = 0; i < n; i++) {
      NR_SearchSpace_t *ss = bwp_dedicated->pdcch_Config->choice.setup->searchSpacesToAddModList->list.array[i];
      if (ss->searchSpaceType->present == target_ss) {
        AssertFatal(ss->controlResourceSetId, "searchSpaceId %ld has a NULL controlResourceSetId\n", ss->searchSpaceId);
        return ss;
      }
    }
    AssertFatal(false, "Couldn't find an adequate SearchSpace for target SearchSpace %d in BWP_DownlinkDedicated\n", target_ss);
  }

  const int n = scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->commonSearchSpaceList->list.count;
  for (int i = 0; i < n; i++) {
    NR_SearchSpace_t *ss =
        scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->commonSearchSpaceList->list.array[i];
    if (ss->searchSpaceType->present == target_ss) {
      AssertFatal(ss->controlResourceSetId, "searchSpaceId %ld has a NULL controlResourceSetId\n", ss->searchSpaceId);
      return ss;
    }
  }
  AssertFatal(false, "Couldn't find an adequate SearchSpace for target SearchSpace %d in ServingCellConfigCommon\n", target_ss);
}

/// @brief Get CORESET resource block parameters from frequency domain resources bitmap.
/// @param coreset Pointer to the CORESET configuratio
/// @param n_rb Output parameter for total # of RBs in the CORESET
/// @param rb_start Output parameter for the starting resource block index of the CORESET
static void get_coreset_rb_params(const NR_ControlResourceSet_t *coreset, uint16_t *n_rb, uint16_t *rb_start)
{
  *n_rb = 0;
  *rb_start = 0;
  
  for (int i = 0; i < 6; i++) {
    for (int t = 0; t < 8; t++) {
      if ((coreset->frequencyDomainResources.buf[i] >> (7 - t)) & 1) {
        if (*n_rb == 0) {
            *rb_start = 48 * i + t * 6;
        }
        *n_rb += 6;  // Each bit represents 6 RBs
      }
    }
  }
}

NR_sched_pdcch_t set_pdcch_structure(gNB_MAC_INST *gNB_mac,
                                     NR_SearchSpace_t *ss,
                                     NR_ControlResourceSet_t *coreset,
                                     NR_ServingCellConfigCommon_t *scc,
                                     NR_BWP_t *bwp,
                                     NR_Type0_PDCCH_CSS_config_t *type0_PDCCH_CSS_config)
{
  AssertFatal(*ss->controlResourceSetId == coreset->controlResourceSetId,
              "coreset id in SS %ld does not correspond to the one in coreset %ld",
              *ss->controlResourceSetId, coreset->controlResourceSetId);

  int sps;
  NR_sched_pdcch_t pdcch;
  if (bwp) { // This is not for SIB1
    if(coreset->controlResourceSetId == 0) {
      pdcch.BWPSize = gNB_mac->cset0_bwp_size;
      pdcch.BWPStart = gNB_mac->cset0_bwp_start;
    } else {
      pdcch.BWPSize = NRRIV2BW(bwp->locationAndBandwidth, MAX_BWP_SIZE);
      pdcch.BWPStart = NRRIV2PRBOFFSET(bwp->locationAndBandwidth, MAX_BWP_SIZE);
    }
    pdcch.SubcarrierSpacing = bwp->subcarrierSpacing;
    pdcch.CyclicPrefix = (bwp->cyclicPrefix == NULL) ? 0 : *bwp->cyclicPrefix;
    //AssertFatal(pdcch_scs==kHz15, "PDCCH SCS above 15kHz not allowed if a symbol above 2 is monitored");
    sps = bwp->cyclicPrefix == NULL ? 14 : 12;
  } else {
    AssertFatal(type0_PDCCH_CSS_config != NULL, "type0_PDCCH_CSS_config is null,bwp %p\n", bwp);
    pdcch.BWPSize = type0_PDCCH_CSS_config->num_rbs;
    pdcch.BWPStart = type0_PDCCH_CSS_config->cset_start_rb;
    pdcch.SubcarrierSpacing = type0_PDCCH_CSS_config->scs_pdcch;
    pdcch.CyclicPrefix = 0;
    sps = 14;
  }

  AssertFatal(ss->monitoringSymbolsWithinSlot != NULL, "ss->monitoringSymbolsWithinSlot is null\n");
  BIT_STRING_t *symbolsInSlot = ss->monitoringSymbolsWithinSlot;
  AssertFatal(symbolsInSlot->buf != NULL, "ss->monitoringSymbolsWithinSlot->buf is null\n");

  // for SPS=14 8 MSBs in positions 13 downto 6
  int monitoringSymbolsWithinSlot = (symbolsInSlot->buf[0] << (sps - 8)) | (symbolsInSlot->buf[1] >> (16 - sps));

  for (int i = 0; i < sps; i++) {
    if ((monitoringSymbolsWithinSlot >> (sps - 1 - i)) & 1) {
      pdcch.StartSymbolIndex = ss->searchSpaceId == 0 ? i + type0_PDCCH_CSS_config->first_symbol_index : i;
      break;
    }
  }

  pdcch.DurationSymbols = coreset->duration;

  //cce-REG-MappingType
  pdcch.CceRegMappingType = coreset->cce_REG_MappingType.present == NR_ControlResourceSet__cce_REG_MappingType_PR_interleaved?
    NFAPI_NR_CCE_REG_MAPPING_INTERLEAVED : NFAPI_NR_CCE_REG_MAPPING_NON_INTERLEAVED;

  if (pdcch.CceRegMappingType == NFAPI_NR_CCE_REG_MAPPING_INTERLEAVED) {
    pdcch.RegBundleSize = (coreset->cce_REG_MappingType.choice.interleaved->reg_BundleSize ==
                                NR_ControlResourceSet__cce_REG_MappingType__interleaved__reg_BundleSize_n6) ? 6 : (2+coreset->cce_REG_MappingType.choice.interleaved->reg_BundleSize);
    pdcch.InterleaverSize = (coreset->cce_REG_MappingType.choice.interleaved->interleaverSize ==
                                  NR_ControlResourceSet__cce_REG_MappingType__interleaved__interleaverSize_n6) ? 6 : (2+coreset->cce_REG_MappingType.choice.interleaved->interleaverSize);
    AssertFatal(scc->physCellId != NULL,"scc->physCellId is null\n");
    pdcch.ShiftIndex = coreset->cce_REG_MappingType.choice.interleaved->shiftIndex != NULL ? *coreset->cce_REG_MappingType.choice.interleaved->shiftIndex : *scc->physCellId;
  }
  else {
    pdcch.RegBundleSize = 6;
    pdcch.InterleaverSize = 0;
    pdcch.ShiftIndex = 0;
  }

  get_coreset_rb_params(coreset, &pdcch.n_rb, &pdcch.rb_start);

  return pdcch;
}

int find_pdcch_candidate(const gNB_MAC_INST *mac,
                         int cc_id,
                         int aggregation,
                         int nr_of_candidates,
                         int beam_idx,
                         const NR_sched_pdcch_t *pdcch,
                         const NR_ControlResourceSet_t *coreset,
                         uint32_t Y)
{
  const uint16_t *vrb_map = mac->common_channels[cc_id].vrb_map[beam_idx];
  const int N_ci = 0;

  const int N_rb = pdcch->n_rb;  // nb of rbs of coreset per symbol
  const int N_symb = coreset->duration; // nb of coreset symbols
  const int N_regs = N_rb * N_symb; // nb of REGs per coreset
  const int N_cces = N_regs / NR_NB_REG_PER_CCE; // nb of cces in coreset
  const int R = pdcch->InterleaverSize;
  const int L = pdcch->RegBundleSize;
  const int C = R > 0 ? N_regs / (L * R) : 0;
  const int B_rb = L / N_symb; // nb of RBs occupied by each REG bundle

  // loop over all the available candidates
  // this implements TS 38.211 Sec. 7.3.2.2
  for(int m = 0; m < nr_of_candidates; m++) { // loop over candidates
    bool taken = false; // flag if the resource for a given candidate are taken
    int first_cce = aggregation * ((Y + ((m * N_cces) / (aggregation * nr_of_candidates)) + N_ci) % (N_cces / aggregation));
    LOG_D(NR_MAC,"Candidate %d of %d first_cce %d (L %d N_cces %d Y %d)\n", m, nr_of_candidates, first_cce, aggregation, N_cces, Y);
    for (int j = first_cce; (j < first_cce + aggregation) && !taken; j++) { // loop over CCEs
      for (int k = 6 * j / L; (k < (6 * j / L + 6 / L)) && !taken; k++) { // loop over REG bundles
        int f = cce_to_reg_interleaving(R, k, pdcch->ShiftIndex, C, L, N_regs);
        for(int rb = 0; rb < B_rb; rb++) { // loop over the RBs of the bundle
          if(vrb_map[pdcch->BWPStart + f * B_rb + rb] & SL_to_bitmap(pdcch->StartSymbolIndex,N_symb)) {
            taken = true;
            break;
          }
        }
      }
    }
    if(!taken)
      return first_cce;
  }
  return -1;
}


int get_cce_index(const gNB_MAC_INST *nrmac,
                  const int CC_id,
                  const int slot,
                  const rnti_t rnti,
                  uint8_t *aggregation_level,
                  int beam_idx,
                  const NR_SearchSpace_t *ss,
                  const NR_ControlResourceSet_t *coreset,
                  NR_sched_pdcch_t *sched_pdcch,
                  float pdcch_cl_adjust)
{
  const uint32_t Y = get_Y(ss, slot, rnti);
  uint8_t nr_of_candidates;

  int agg_level_search_order[NUM_PDCCH_AGG_LEVELS];
  determine_aggregation_level_search_order(agg_level_search_order, pdcch_cl_adjust);

  for (int i = 0; i < NUM_PDCCH_AGG_LEVELS; i++) {
    find_aggregation_candidates(aggregation_level, &nr_of_candidates, ss, 1 << agg_level_search_order[i]);
    if (nr_of_candidates > 0)
      break;
  }
  int CCEIndex = find_pdcch_candidate(nrmac, CC_id, *aggregation_level, nr_of_candidates, beam_idx, sched_pdcch, coreset, Y);
  return CCEIndex;
}

void fill_pdcch_vrb_map(gNB_MAC_INST *mac,
                        int CC_id,
                        NR_sched_pdcch_t *pdcch,
                        int first_cce,
                        int aggregation,
                        int beam)
{
  uint16_t *vrb_map = mac->common_channels[CC_id].vrb_map[beam];

  int N_rb = pdcch->n_rb; // nb of rbs of coreset per symbol
  int L = pdcch->RegBundleSize;
  int R = pdcch->InterleaverSize;
  int n_shift = pdcch->ShiftIndex;
  int N_symb = pdcch->DurationSymbols;
  int N_regs = N_rb*N_symb; // nb of REGs per coreset
  int B_rb = L/N_symb; // nb of RBs occupied by each REG bundle
  int C = R>0 ? N_regs/(L*R) : 0;

  for (int j=first_cce; j<first_cce+aggregation; j++) { // loop over CCEs
    for (int k=6*j/L; k<(6*j/L+6/L); k++) { // loop over REG bundles
      int f = cce_to_reg_interleaving(R, k, n_shift, C, L, N_regs);
      for(int rb=0; rb<B_rb; rb++) // loop over the RBs of the bundle
        vrb_map[pdcch->BWPStart + f*B_rb + rb] |= SL_to_bitmap(pdcch->StartSymbolIndex, N_symb);
    }
  }
}

static bool multiple_2_3_5(int rb)
{
  while (rb % 2 == 0)
    rb /= 2;
  while (rb % 3 == 0)
    rb /= 3;
  while (rb % 5 == 0)
    rb /= 5;

  return (rb == 1);
}

bool nr_find_nb_rb(uint16_t Qm,
                   uint16_t R,
                   long transform_precoding,
                   uint8_t nrOfLayers,
                   uint16_t nb_symb_sch,
                   uint16_t nb_dmrs_prb,
                   uint32_t bytes,
                   uint16_t nb_rb_min,
                   uint16_t nb_rb_max,
                   uint32_t *tbs,
                   uint16_t *nb_rb)
{
  // for transform precoding only RB = 2^a_2 * 3^a_3 * 5^a_5 is allowed with a non-negative
  while(transform_precoding == NR_PUSCH_Config__transformPrecoder_enabled &&
        !multiple_2_3_5(nb_rb_max))
    nb_rb_max--;

  /* is the maximum (not even) enough? */
  *nb_rb = nb_rb_max;
  *tbs = nr_compute_tbs(Qm, R, *nb_rb, nb_symb_sch, nb_dmrs_prb, 0, 0, nrOfLayers) >> 3;
  /* check whether it does not fit, or whether it exactly fits. Some algorithms
   * might depend on the return value! */
  if (bytes > *tbs)
    return false;
  if (bytes == *tbs)
    return true;

  /* is the minimum enough? */
  *nb_rb = nb_rb_min;
  *tbs = nr_compute_tbs(Qm, R, *nb_rb, nb_symb_sch, nb_dmrs_prb, 0, 0, nrOfLayers) >> 3;
  if (bytes <= *tbs)
    return true;

  /* perform binary search to allocate all bytes within a TBS up to nb_rb_max
   * RBs */
  int hi = nb_rb_max;
  int lo = nb_rb_min;
  for (int p = (hi + lo) / 2; lo + 1 < hi; p = (hi + lo) / 2) {
    // for transform precoding only RB = 2^a_2 * 3^a_3 * 5^a_5 is allowed with a non-negative
    while(transform_precoding == NR_PUSCH_Config__transformPrecoder_enabled &&
          !multiple_2_3_5(p))
      p++;

    // If by increasing p for transform precoding we already hit the high, break to avoid infinite loop
    if (p == hi)
      break;

    const uint32_t TBS = nr_compute_tbs(Qm, R, p, nb_symb_sch, nb_dmrs_prb, 0, 0, nrOfLayers) >> 3;
    if (bytes == TBS) {
      hi = p;
      break;
    } else if (bytes < TBS) {
      hi = p;
    } else {
      lo = p;
    }
  }
  *nb_rb = hi;
  *tbs = nr_compute_tbs(Qm, R, *nb_rb, nb_symb_sch, nb_dmrs_prb, 0, 0, nrOfLayers) >> 3;
  /* return whether we could allocate all bytes and stay below nb_rb_max */
  return *tbs >= bytes && *nb_rb <= nb_rb_max;
}

const NR_DMRS_UplinkConfig_t *get_DMRS_UplinkConfig(const NR_PUSCH_Config_t *pusch_Config, const NR_tda_info_t *tda_info)
{
  if (pusch_Config == NULL)
    return NULL;

  return tda_info->mapping_type == typeA ? pusch_Config->dmrs_UplinkForPUSCH_MappingTypeA->choice.setup
                                         : pusch_Config->dmrs_UplinkForPUSCH_MappingTypeB->choice.setup;
}

NR_pusch_dmrs_t get_ul_dmrs_params(const NR_ServingCellConfigCommon_t *scc,
                                   const NR_UE_UL_BWP_t *ul_bwp,
                                   const NR_tda_info_t *tda_info,
                                   const int Layers)
{
  NR_pusch_dmrs_t dmrs = {0};
  // TODO setting of cdm groups with no data to be redone for MIMO
  if(NFAPI_MODE == NFAPI_MODE_AERIAL) {
    dmrs.num_dmrs_cdm_grps_no_data = 2;
  } else {
    if (ul_bwp->transform_precoding && Layers < 3)
      dmrs.num_dmrs_cdm_grps_no_data = ul_bwp->dci_format == NR_UL_DCI_FORMAT_0_1 || tda_info->nrOfSymbols <= 2 ? 1 : 2;
    else
      dmrs.num_dmrs_cdm_grps_no_data = 2;
  }

  const NR_DMRS_UplinkConfig_t *NR_DMRS_UplinkConfig = get_DMRS_UplinkConfig(ul_bwp->pusch_Config, tda_info);
  dmrs.ptrsConfig = NR_DMRS_UplinkConfig
                    && NR_DMRS_UplinkConfig->phaseTrackingRS ?
                    NR_DMRS_UplinkConfig->phaseTrackingRS->choice.setup : NULL;

  dmrs.pusch_identity = *scc->physCellId;
  dmrs.dmrs_scrambling_id = *scc->physCellId;
  dmrs.scid = 0;
  if (ul_bwp->transform_precoding) { // transform precoding disabled
    long *scramblingid = NULL;
    if (NR_DMRS_UplinkConfig && dmrs.scid == 0)
      scramblingid = NR_DMRS_UplinkConfig->transformPrecodingDisabled->scramblingID0;
    else if (NR_DMRS_UplinkConfig)
      scramblingid = NR_DMRS_UplinkConfig->transformPrecodingDisabled->scramblingID1;
    if (scramblingid)
      dmrs.dmrs_scrambling_id = *scramblingid;
  } else {
    if (NR_DMRS_UplinkConfig && NR_DMRS_UplinkConfig->transformPrecodingEnabled) {
      if (NR_DMRS_UplinkConfig->transformPrecodingEnabled->nPUSCH_Identity)
        dmrs.pusch_identity = *NR_DMRS_UplinkConfig->transformPrecodingEnabled->nPUSCH_Identity;
      if ((!NR_DMRS_UplinkConfig->transformPrecodingEnabled->sequenceGroupHopping
          && !NR_DMRS_UplinkConfig->transformPrecodingEnabled->sequenceHopping)
          && !scc->uplinkConfigCommon->initialUplinkBWP->pusch_ConfigCommon->choice.setup->groupHoppingEnabledTransformPrecoding)
        dmrs.low_papr_sequence_number = 0;
      else
        AssertFatal(false, "Hopping mode is not supported in transform precoding\n");
    }
  }

  dmrs.dmrs_config_type = NR_DMRS_UplinkConfig && NR_DMRS_UplinkConfig->dmrs_Type ? 1 : 0;
  const pusch_dmrs_AdditionalPosition_t additional_pos = (NR_DMRS_UplinkConfig && NR_DMRS_UplinkConfig->dmrs_AdditionalPosition) ?
                                                         (*NR_DMRS_UplinkConfig->dmrs_AdditionalPosition ==
                                                         NR_DMRS_UplinkConfig__dmrs_AdditionalPosition_pos3 ?
                                                         3 : *NR_DMRS_UplinkConfig->dmrs_AdditionalPosition) : 2;

  const pusch_maxLength_t pusch_maxLength = NR_DMRS_UplinkConfig ? (NR_DMRS_UplinkConfig->maxLength == NULL ? 1 : 2) : 1;
  dmrs.ul_dmrs_symb_pos = get_l_prime(tda_info->nrOfSymbols,
                                       tda_info->mapping_type,
                                       additional_pos,
                                       pusch_maxLength,
                                       tda_info->startSymbolIndex,
                                       scc->dmrs_TypeA_Position);

  dmrs.num_dmrs_symb = count_bits64_with_mask(dmrs.ul_dmrs_symb_pos, tda_info->startSymbolIndex, tda_info->nrOfSymbols);
  dmrs.N_PRB_DMRS = dmrs.num_dmrs_cdm_grps_no_data * (dmrs.dmrs_config_type == 0 ? 6 : 4);
  return dmrs;
}

#define BLER_UPDATE_FRAME 10
#define BLER_FILTER 0.9f
int get_mcs_from_bler(const NR_bler_options_t *bler_options,
                      const NR_mac_dir_stats_t *stats,
                      NR_bler_stats_t *bler_stats,
                      int max_mcs,
                      frame_t frame)
{
  int diff = frame - bler_stats->last_frame;
  if (diff < 0) // wrap around
    diff += 1024;

  max_mcs = min(max_mcs, bler_options->max_mcs);
  const uint8_t old_mcs = min(bler_stats->mcs, max_mcs);
  if (diff < BLER_UPDATE_FRAME)
    return old_mcs; // no update

  // last update is longer than x frames ago
  const int num_dl_sched = (int)(stats->rounds[0] - bler_stats->rounds[0]);
  const int num_dl_retx = (int)(stats->rounds[1] - bler_stats->rounds[1]);
  const float bler_window = num_dl_sched > 0 ? (float) num_dl_retx / num_dl_sched : bler_stats->bler;
  bler_stats->bler = BLER_FILTER * bler_stats->bler + (1 - BLER_FILTER) * bler_window;

  int new_mcs = old_mcs;
  if (bler_stats->bler < bler_options->lower && old_mcs < max_mcs && num_dl_sched > 3)
    new_mcs += 1;
  else if (bler_stats->bler > bler_options->upper || num_dl_sched <= 3) // above threshold or no activity
    new_mcs -= 1;
  // else we are within threshold boundaries

  new_mcs = max(new_mcs, bler_options->min_mcs);
  bler_stats->last_frame = frame;
  bler_stats->mcs = new_mcs;
  memcpy(bler_stats->rounds, stats->rounds, sizeof(stats->rounds));
  LOG_D(MAC, "frame %4d MCS %d -> %d (num_dl_sched %d, num_dl_retx %d, BLER wnd %.3f avg %.6f)\n",
        frame, old_mcs, new_mcs, num_dl_sched, num_dl_retx, bler_window, bler_stats->bler);
  return new_mcs;
}

nfapi_nr_dl_dci_pdu_t *prepare_dci_pdu(nfapi_nr_dl_tti_pdcch_pdu_rel15_t *pdcch_pdu,
                                       const NR_ServingCellConfigCommon_t *scc,
                                       const NR_SearchSpace_t *ss,
                                       const NR_ControlResourceSet_t *coreset,
                                       int aggregation_level,
                                       int cce_index,
                                       int beam_index,
                                       int rnti)
{
  nfapi_nr_dl_dci_pdu_t *dci_pdu = &pdcch_pdu->dci_pdu[pdcch_pdu->numDlDci];
  dci_pdu->RNTI = rnti;
  dci_pdu->AggregationLevel = aggregation_level;
  dci_pdu->CceIndex = cce_index;
  if (coreset->pdcch_DMRS_ScramblingID &&
      ss->searchSpaceType->present == NR_SearchSpace__searchSpaceType_PR_ue_Specific) {
    dci_pdu->ScramblingId = *coreset->pdcch_DMRS_ScramblingID;
    dci_pdu->ScramblingRNTI = rnti;
  } else {
    dci_pdu->ScramblingId = *scc->physCellId;
    dci_pdu->ScramblingRNTI = 0;
  }

  uint16_t N_rb = 0;
  uint16_t rb_start = 0;
  get_coreset_rb_params(coreset, &N_rb, &rb_start);

  dci_pdu->beta_PDCCH_1_0 = 0;
  dci_pdu->powerControlOffsetSS = 1;
  dci_pdu->precodingAndBeamforming.num_prgs = 1;
  dci_pdu->precodingAndBeamforming.prg_size = N_rb;
  dci_pdu->precodingAndBeamforming.dig_bf_interfaces = 1;
  dci_pdu->precodingAndBeamforming.prgs_list[0].pm_idx = 0;
  dci_pdu->precodingAndBeamforming.prgs_list[0].dig_bf_interface_list[0].beam_idx = beam_index;
  return dci_pdu;
}

dci_pdu_rel15_t prepare_dci_dl_payload(const gNB_MAC_INST *gNB_mac,
                                       const NR_UE_info_t *UE,
                                       nr_rnti_type_t rnti_type,
                                       NR_SearchSpace__searchSpaceType_PR ss_type,
                                       const nfapi_nr_dl_tti_pdsch_pdu_rel15_t *pdsch_pdu,
                                       const NR_sched_pdsch_t *sched_pdsch,
                                       const NR_sched_pucch_t *pucch,
                                       int harq_pid,
                                       int tb_scaling,
                                       bool is_sib1)
{
  const NR_UE_DL_BWP_t *dl_BWP = UE ? &UE->current_DL_BWP : NULL;
  dci_pdu_rel15_t dci_payload = {0};
  dci_payload.format_indicator = 1;
  dci_payload.time_domain_assignment.val = sched_pdsch->time_domain_allocation;
  dci_payload.mcs = sched_pdsch->mcs;
  dci_payload.rv = pdsch_pdu->rvIndex[0];
  dci_payload.vrb_to_prb_mapping.val = 0;
  int riv_bwp = pdsch_pdu->BWPSize;
  if (!UE)
    riv_bwp = gNB_mac->cset0_bwp_size;
  else if (dl_BWP->dci_format == NR_DL_DCI_FORMAT_1_0 && ss_type == NR_SearchSpace__searchSpaceType_PR_common) {
    if (gNB_mac->cset0_bwp_size > 0)
      riv_bwp = gNB_mac->cset0_bwp_size;
    else
      riv_bwp = UE->sc_info.initial_dl_BWPSize;
  }
  dci_payload.frequency_domain_assignment.val = PRBalloc_to_locationandbandwidth0(pdsch_pdu->rbSize, pdsch_pdu->rbStart, riv_bwp);
  if (rnti_type == TYPE_SI_RNTI_) {
    dci_payload.system_info_indicator = !is_sib1;
    return dci_payload;
  }
  if (rnti_type == TYPE_RA_RNTI_) {
    dci_payload.tb_scaling = tb_scaling;
    return dci_payload;
  }

  const NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
  dci_payload.dmrs_sequence_initialization.val = pdsch_pdu->SCID;
  dci_payload.antenna_ports.val = sched_pdsch->dmrs_parms.dmrs_ports_id;
  dci_payload.tpc = sched_ctrl->tpc1;
  const NR_UE_harq_t *harq = &sched_ctrl->harq_processes[harq_pid];
  AssertFatal(harq, "HARQ process should be available for DCI with RNTI %s\n", rnti_types(rnti_type));
  dci_payload.harq_pid.val = harq_pid;
  dci_payload.ndi = harq->ndi;
  dci_payload.pucch_resource_indicator = pucch ? pucch->resource_indicator : 0;
  dci_payload.pdsch_to_harq_feedback_timing_indicator.val = pucch ? pucch->timing_indicator : 0; // PDSCH to HARQ TI
  dci_payload.dai[0].val = pucch ? (pucch->dai_c - 1) & 3 : 0;
  // bwp indicator
  // as per table 7.3.1.1.2-1 in 38.212
  if (dl_BWP)
    dci_payload.bwp_indicator.val = UE->sc_info.n_dl_bwp < 4 ? dl_BWP->bwp_id : dl_BWP->bwp_id - 1;
  return dci_payload;
}

static uint32_t compute_srs_resource_indicator(long *maxMIMO_Layers,
                                               NR_PUSCH_Config_t *pusch_Config,
                                               NR_SRS_Config_t *srs_config,
                                               nr_srs_feedback_t *srs_feedback)
{
  uint32_t val = 0;
  if (srs_config && pusch_Config && pusch_Config->txConfig != NULL) {
    if (*pusch_Config->txConfig == NR_PUSCH_Config__txConfig_codebook) {

      // TS 38.212 - Section 7.3.1.1.2: SRS resource indicator has ceil(log2(N_SRS)) bits according to
      // Tables 7.3.1.1.2-32, 7.3.1.1.2-32A and 7.3.1.1.2-32B if the higher layer parameter txConfig = codebook,
      // where N_SRS is the number of configured SRS resources in the SRS resource set configured by higher layer
      // parameter srs-ResourceSetToAddModList, and associated with the higher layer parameter usage of value codeBook.
      int count = srs_codebook_nb_res(srs_config);
      if (count > 1)
        val = table_7_3_1_1_2_32[count - 2][srs_feedback->sri];
    } else {
      // TS 38.212 - Section 7.3.1.1.2: SRS resource indicator has ceil(log2(sum(k = 1 until min(Lmax,N_SRS) of binomial(N_SRS,k))))
      // bits according to Tables 7.3.1.1.2-28/29/30/31 if the higher layer parameter txConfig = nonCodebook, where
      // N_SRS is the number of configured SRS resources in the SRS resource set configured by higher layer parameter
      // srs-ResourceSetToAddModList, and associated with the higher layer parameter usage of value nonCodeBook and:
      //
      // - if UE supports operation with maxMIMO-Layers and the higher layer parameter maxMIMO-Layers of
      // PUSCH-ServingCellConfig of the serving cell is configured, Lmax is given by that parameter;
      //
      // - otherwise, Lmax is given by the maximum number of layers for PUSCH supported by the UE for the serving cell
      // for non-codebook based operation.
      int Lmax = 0;
      if (maxMIMO_Layers != NULL)
        Lmax = *maxMIMO_Layers;
      else
        AssertFatal(false, "MIMO on PUSCH not supported, maxMIMO_Layers needs to be set to 1\n");
      int count = srs_non_codebook_nb_res(srs_config);
      int lsum = srs_binomial_sum(count, Lmax);
      if (lsum > 0 && ceil(log2(lsum)) > 0) {
        switch(Lmax) {
          case 1:
            val = table_7_3_1_1_2_28[count-2][srs_feedback->sri];
            break;
          case 2:
            val = table_7_3_1_1_2_29[count-2][srs_feedback->sri];
            break;
          case 3:
            val = table_7_3_1_1_2_30[count-2][srs_feedback->sri];
            break;
          case 4:
            val = table_7_3_1_1_2_31[count-2][srs_feedback->sri];
            break;
          default:
            LOG_E(NR_MAC, "%s (%d) - Invalid Lmax %d\n", __FUNCTION__, __LINE__, Lmax);
        }
      }
    }
  }
  return val;
}

static uint32_t compute_precoding_information(NR_PUSCH_Config_t *pusch_Config,
                                              NR_SRS_Config_t *srs_config,
                                              dci_field_t srs_resource_indicator,
                                              nr_srs_feedback_t *srs_feedback,
                                              const uint8_t *nrOfLayers,
                                              int *tpmi)
{
  uint32_t val = 0;

  uint8_t pusch_antenna_ports = get_pusch_nb_antenna_ports(pusch_Config, srs_config, srs_resource_indicator);
  if (!pusch_Config
      || (pusch_Config->txConfig != NULL && *pusch_Config->txConfig == NR_PUSCH_Config__txConfig_nonCodebook)
      || pusch_antenna_ports == 1) {
    return val;
  }

  long max_rank = *pusch_Config->maxRank;
  long *ul_FullPowerTransmission = pusch_Config->ext1 ? pusch_Config->ext1->ul_FullPowerTransmission_r16 : NULL;
  long *codebookSubset = pusch_Config->codebookSubset;
  int ul_tpmi = tpmi ? *tpmi : srs_feedback ? srs_feedback->tpmi : -1;

  if (pusch_antenna_ports == 2) {

    if (max_rank == 1) {
      // - 1 or 3 bits according to Table 7.3.1.1.2-5 for 2 antenna ports, if txConfig = codebook, ul-FullPowerTransmission
      //   is not configured or configured to fullpowerMode2 or configured to fullpower, and according to whether transform
      //   precoder is enabled or disabled, and the values of higher layer parameters maxRank and codebookSubset;
      // - 2 bits according to Table 7.3.1.1.2-5A for 2 antenna ports, if txConfig = codebook, ul-FullPowerTransmission =
      //   fullpowerMode1, maxRank=1, and according to whether transform precoder is enabled or disabled, and the values
      //   of higher layer parameter codebookSubset;
      if (ul_FullPowerTransmission && *ul_FullPowerTransmission == NR_PUSCH_Config__ext1__ul_FullPowerTransmission_r16_fullpowerMode1) {
        AssertFatal(ul_tpmi <= 2,"TPMI %d is invalid!\n", ul_tpmi);
        val = ul_tpmi;
      } else {
        if (codebookSubset && *codebookSubset == NR_PUSCH_Config__codebookSubset_nonCoherent) {
          AssertFatal(ul_tpmi <= 1,"TPMI %d is invalid!\n", ul_tpmi);
          val = ul_tpmi;
        } else {
          AssertFatal(ul_tpmi <= 5,"TPMI %d is invalid!\n", ul_tpmi);
          val = ul_tpmi;
        }
      }
    } else {
      // - 2 or 4 bits according to Table 7.3.1.1.2-4 for 2 antenna ports, if txConfig = codebook, ul-FullPowerTransmission
      //   is not configured or configured to fullpowerMode2 or configured to fullpower, and according to whether transform
      //   precoder is enabled or disabled, and the values of higher layer parameters maxRank and codebookSubset;
      // - 2 bits according to Table 7.3.1.1.2-4A for 2 antenna ports, if txConfig = codebook, ul-FullPowerTransmission =
      //   fullpowerMode1, transform precoder is disabled, maxRank=2, and codebookSubset=nonCoherent;
      if (ul_FullPowerTransmission && *ul_FullPowerTransmission == NR_PUSCH_Config__ext1__ul_FullPowerTransmission_r16_fullpowerMode1) {
        AssertFatal((*nrOfLayers==1 && ul_tpmi <= 2)
                    || (*nrOfLayers==2 && ul_tpmi == 0),
                    "TPMI %d is invalid!\n",
                    ul_tpmi);
        val = *nrOfLayers == 1 ? table_7_3_1_1_2_4A_1layer[ul_tpmi] : 2;
      } else {
        if (codebookSubset && *codebookSubset == NR_PUSCH_Config__codebookSubset_nonCoherent) {
          AssertFatal((*nrOfLayers==1 && ul_tpmi <= 1)
                      || (*nrOfLayers==2 && ul_tpmi == 0),
                      "TPMI %d is invalid!\n",
                      ul_tpmi);
          val = *nrOfLayers == 1 ? ul_tpmi : 2;
        } else {
          AssertFatal((*nrOfLayers==1 && ul_tpmi <= 5)
                      || (*nrOfLayers==2 && ul_tpmi <= 2),
                      "TPMI %d is invalid!\n",
                      ul_tpmi);
          val = *nrOfLayers == 1 ? table_7_3_1_1_2_4_1layer_fullyAndPartialAndNonCoherent[ul_tpmi] :
                                   table_7_3_1_1_2_4_2layers_fullyAndPartialAndNonCoherent[ul_tpmi];
        }
      }
    }

  } else if (pusch_antenna_ports == 4) {

    if (max_rank == 1) {
      // - 2, 4, or 5 bits according to Table 7.3.1.1.2-3 for 4 antenna ports, if txConfig = codebook, ul-FullPowerTransmission
      //   is not configured or configured to fullpowerMode2 or configured to fullpower, and according to whether transform
      //   precoder is enabled or disabled, and the values of higher layer parameters maxRank, and codebookSubset;
      // - 3 or 4 bits according to Table 7.3.1.1.2-3A for 4 antenna ports, if txConfig = codebook, ul-FullPowerTransmission =
      //   fullpowerMode1, maxRank=1, and according to whether transform precoder is enabled or disabled, and the values
      //   of higher layer parameter codebookSubset;
      if (ul_FullPowerTransmission && *ul_FullPowerTransmission == NR_PUSCH_Config__ext1__ul_FullPowerTransmission_r16_fullpowerMode1) {
        if (codebookSubset && *codebookSubset == NR_PUSCH_Config__codebookSubset_nonCoherent) {
           AssertFatal(ul_tpmi <= 3 || ul_tpmi == 13, "TPMI %d is invalid!\n", ul_tpmi);
        } else {
          AssertFatal(ul_tpmi <= 15, "TPMI %d is invalid!\n", ul_tpmi);
        }
        val = table_7_3_1_1_2_3A[ul_tpmi];
      } else {
        if (codebookSubset && *codebookSubset == NR_PUSCH_Config__codebookSubset_nonCoherent) {
          AssertFatal(ul_tpmi <= 3, "TPMI %d is invalid!\n", ul_tpmi);
        } else if (codebookSubset && *codebookSubset == NR_PUSCH_Config__codebookSubset_partialAndNonCoherent) {
          AssertFatal(ul_tpmi <= 11, "TPMI %d is invalid!\n", ul_tpmi);
        } else {
          AssertFatal(ul_tpmi <= 27, "TPMI %d is invalid!\n", ul_tpmi);
        }
        val = ul_tpmi;
      }
    } else {
      // - 4, 5, or 6 bits according to Table 7.3.1.1.2-2 for 4 antenna ports, if txConfig = codebook, ul-FullPowerTransmission
      //   is not configured or configured to fullpowerMode2 or configured to fullpower, and according to whether transform
      //   precoder is enabled or disabled, and the values of higher layer parameters maxRank, and codebookSubset;
      // - 4 or 5 bits according to Table 7.3.1.1.2-2A for 4 antenna ports, if txConfig = codebook, ul-FullPowerTransmission =
      //   fullpowerMode1, maxRank=2, transform precoder is disabled, and according to the values of higher layer parameter
      //   codebookSubset;
      // - 4 or 6 bits according to Table 7.3.1.1.2-2B for 4 antenna ports, if txConfig = codebook, ul-FullPowerTransmission =
      //   fullpowerMode1, maxRank=3 or 4, transform precoder is disabled, and according to the values of higher layer
      //   parameter codebookSubset;
      if (ul_FullPowerTransmission && *ul_FullPowerTransmission == NR_PUSCH_Config__ext1__ul_FullPowerTransmission_r16_fullpowerMode1) {
        if (max_rank == 2) {
          if (codebookSubset && *codebookSubset == NR_PUSCH_Config__codebookSubset_nonCoherent) {
            AssertFatal((*nrOfLayers==1
                        && (ul_tpmi <= 3 || ul_tpmi == 13))
                        || (*nrOfLayers == 2 && ul_tpmi <= 6),
                        "TPMI %d is invalid!\n",
                        ul_tpmi);
          } else {
            AssertFatal((*nrOfLayers == 1 && ul_tpmi <= 15)
                        || (*nrOfLayers == 2 && ul_tpmi <= 13),
                        "TPMI %d is invalid!\n",
                        ul_tpmi);
          }
          val = *nrOfLayers == 1 ? table_7_3_1_1_2_2A_1layer[ul_tpmi] : table_7_3_1_1_2_2A_2layers[ul_tpmi];
        } else {
          if (codebookSubset && *codebookSubset == NR_PUSCH_Config__codebookSubset_nonCoherent) {
            AssertFatal((*nrOfLayers == 1
                        && (ul_tpmi <= 3 || ul_tpmi == 13))
                        || (*nrOfLayers == 2 && ul_tpmi <= 6)
                        || (*nrOfLayers == 3 && ul_tpmi <= 1)
                        || (*nrOfLayers == 4 && ul_tpmi == 0),
                        "TPMI %d is invalid!\n",
                        ul_tpmi);
          } else {
            AssertFatal((*nrOfLayers == 1 && ul_tpmi <= 15)
                        || (*nrOfLayers == 2 && ul_tpmi <= 13)
                        || (*nrOfLayers == 3 && ul_tpmi <= 2)
                        || (*nrOfLayers == 4 && ul_tpmi <= 2),
                        "TPMI %d is invalid!\n",
                        ul_tpmi);
          }
          switch (*nrOfLayers) {
            case 1:
              val = table_7_3_1_1_2_2B_1layer[ul_tpmi];
              break;
            case 2:
              val = table_7_3_1_1_2_2B_2layers[ul_tpmi];
              break;
            case 3:
              val = table_7_3_1_1_2_2B_3layers[ul_tpmi];
              break;
            case 4:
              val = table_7_3_1_1_2_2B_4layers[ul_tpmi];
              break;
            default:
              LOG_E(NR_MAC,"Number of layers %d is invalid!\n", *nrOfLayers);
          }
        }
      } else {
        if (codebookSubset && *codebookSubset == NR_PUSCH_Config__codebookSubset_nonCoherent) {
          AssertFatal((*nrOfLayers == 1 && ul_tpmi <= 3)
                      || (*nrOfLayers == 2 && ul_tpmi <= 5)
                      || (*nrOfLayers == 3 && ul_tpmi == 0)
                      || (*nrOfLayers == 4 && ul_tpmi == 0),
                      "TPMI %d is invalid!\n",
                      ul_tpmi);
        } else if (codebookSubset && *codebookSubset == NR_PUSCH_Config__codebookSubset_partialAndNonCoherent) {
          AssertFatal((*nrOfLayers == 1 && ul_tpmi <= 11)
                      || (*nrOfLayers == 2 && ul_tpmi <= 13)
                      || (*nrOfLayers == 3 && ul_tpmi <= 2)
                      || (*nrOfLayers == 4 && ul_tpmi <= 2),
                      "TPMI %d is invalid!\n",
                      ul_tpmi);
        } else {
          AssertFatal((*nrOfLayers == 1 && ul_tpmi <= 28)
                      || (*nrOfLayers == 2 && ul_tpmi <= 22)
                      || (*nrOfLayers == 3 && ul_tpmi <= 7)
                      || (*nrOfLayers == 4 && ul_tpmi <= 5),
                      "TPMI %d is invalid!\n",
                      ul_tpmi);
        }
        switch (*nrOfLayers) {
          case 1:
            val = table_7_3_1_1_2_2_1layer[ul_tpmi];
            break;
          case 2:
            val = table_7_3_1_1_2_2_2layers[ul_tpmi];
            break;
          case 3:
            val = table_7_3_1_1_2_2_3layers[ul_tpmi];
            break;
          case 4:
            val = table_7_3_1_1_2_2_4layers[ul_tpmi];
            break;
          default:
            LOG_E(NR_MAC,"Number of layers %d is invalid!\n", *nrOfLayers);
        }
      }
    }
  }
  return val;
}

void config_uldci(const NR_UE_ServingCell_Info_t *sc_info,
                  const nfapi_nr_pusch_pdu_t *pusch_pdu,
                  dci_pdu_rel15_t *dci_pdu_rel15,
                  nr_srs_feedback_t *srs_feedback,
                  int *tpmi,
                  int time_domain_assignment,
                  uint8_t tpc,
                  uint8_t ndi,
                  NR_UE_UL_BWP_t *ul_bwp,
                  NR_SearchSpace__searchSpaceType_PR ss_type)
{
  int bwp_id = ul_bwp->bwp_id;
  nr_dci_format_t dci_format = ul_bwp->dci_format;

  // 3GPP TS 38.214 Section 6.1.2.2.2 Uplink resource allocation type 1
  uint16_t riv_bwp_size = ul_bwp->BWPSize;
  if (dci_format == NR_UL_DCI_FORMAT_0_0 && ss_type == NR_SearchSpace__searchSpaceType_PR_common && ul_bwp->pusch_Config
      && ul_bwp->pusch_Config->resourceAllocation == NR_PUSCH_Config__resourceAllocation_resourceAllocationType1) {
    riv_bwp_size = sc_info->initial_ul_BWPSize;
  }
  dci_pdu_rel15->frequency_domain_assignment.val =
      PRBalloc_to_locationandbandwidth0(pusch_pdu->rb_size, pusch_pdu->rb_start, riv_bwp_size);
  dci_pdu_rel15->time_domain_assignment.val = time_domain_assignment;
  dci_pdu_rel15->frequency_hopping_flag.val = pusch_pdu->frequency_hopping;
  dci_pdu_rel15->mcs = pusch_pdu->mcs_index;
  dci_pdu_rel15->ndi = ndi;
  dci_pdu_rel15->rv = pusch_pdu->pusch_data.rv_index;
  dci_pdu_rel15->harq_pid.val = pusch_pdu->pusch_data.harq_process_id;
  dci_pdu_rel15->tpc = tpc;

  NR_PUSCH_Config_t *pusch_Config = ul_bwp->pusch_Config;
  if (pusch_Config)
    AssertFatal(pusch_Config->resourceAllocation == NR_PUSCH_Config__resourceAllocation_resourceAllocationType1,
                "Only frequency resource allocation type 1 is currently supported\n");

  switch (dci_format) {
    case NR_UL_DCI_FORMAT_0_0:
      dci_pdu_rel15->format_indicator = 0;
      break;
    case NR_UL_DCI_FORMAT_0_1:
      LOG_D(NR_MAC,"Configuring DCI Format 0_1\n");
      dci_pdu_rel15->dai[0].val = 0; //TODO
      // bwp indicator as per table 7.3.1.1.2-1 in 38.212
      dci_pdu_rel15->bwp_indicator.val = sc_info->n_ul_bwp < 4 ? bwp_id : bwp_id - 1;
      // SRS resource indicator
      if (pusch_Config && pusch_Config->txConfig != NULL) {
        AssertFatal(*pusch_Config->txConfig == NR_PUSCH_Config__txConfig_codebook,
                    "Non Codebook configuration non supported\n");
        dci_pdu_rel15->srs_resource_indicator.val = compute_srs_resource_indicator(sc_info->maxMIMO_Layers_PUSCH,
                                                                                   pusch_Config,
                                                                                   ul_bwp->srs_Config,
                                                                                   srs_feedback);
      }
      dci_pdu_rel15->precoding_information.val = compute_precoding_information(pusch_Config,
                                                                               ul_bwp->srs_Config,
                                                                               dci_pdu_rel15->srs_resource_indicator,
                                                                               srs_feedback,
                                                                               &pusch_pdu->nrOfLayers,
                                                                               tpmi);

      // antenna_ports.val = 0 for transform precoder is disabled, dmrs-Type=1, maxLength=1, Rank=1/2/3/4
      // Antenna Ports

      dci_pdu_rel15->antenna_ports.val = NFAPI_MODE == NFAPI_MODE_AERIAL ? 2 : 0;

      // DMRS sequence initialization
      dci_pdu_rel15->dmrs_sequence_initialization.val = pusch_pdu->scid;
      break;
    default :
      AssertFatal(0, "Valid UL formats are 0_0 and 0_1\n");
  }

  LOG_D(NR_MAC,
        "%s() ULDCI type 0 payload: dci_format %d, freq_alloc %d, time_alloc %d, freq_hop_flag %d, precoding_information.val %d antenna_ports.val %d mcs %d tpc %d ndi %d rv %d\n",
        __func__,
        dci_format,
        dci_pdu_rel15->frequency_domain_assignment.val,
        dci_pdu_rel15->time_domain_assignment.val,
        dci_pdu_rel15->frequency_hopping_flag.val,
        dci_pdu_rel15->precoding_information.val,
        dci_pdu_rel15->antenna_ports.val,
        dci_pdu_rel15->mcs,
        dci_pdu_rel15->tpc,
        dci_pdu_rel15->ndi,
        dci_pdu_rel15->rv);
}

const int default_pucch_fmt[]       = {0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1};
const int default_pucch_firstsymb[] = {12,12,12,10,10,10,10,4,4,4,4,0,0,0,0,0};
const int default_pucch_numbsymb[]  = {2,2,2,2,4,4,4,4,10,10,10,10,14,14,14,14,14};
const int default_pucch_prboffset[] = {0,0,3,0,0,2,4,0,0,2,4,0,0,2,4,-1};
const int default_pucch_csset[]     = {2,3,3,2,4,4,4,2,4,4,4,2,4,4,4,4};

int nr_get_default_pucch_res(int pucch_ResourceCommon) {

  AssertFatal(pucch_ResourceCommon>=0 && pucch_ResourceCommon < 16, "illegal pucch_ResourceCommon %d\n",pucch_ResourceCommon);

  return(default_pucch_csset[pucch_ResourceCommon]);
}

void nr_configure_pdcch(nfapi_nr_dl_tti_pdcch_pdu_rel15_t *pdcch_pdu, NR_ControlResourceSet_t *coreset, NR_sched_pdcch_t *pdcch)
{
  pdcch_pdu->BWPSize = pdcch->BWPSize;
  pdcch_pdu->BWPStart = pdcch->BWPStart;
  pdcch_pdu->SubcarrierSpacing = pdcch->SubcarrierSpacing;
  pdcch_pdu->CyclicPrefix = pdcch->CyclicPrefix;
  pdcch_pdu->StartSymbolIndex = pdcch->StartSymbolIndex;

  pdcch_pdu->DurationSymbols  = coreset->duration;

  for (int i = 0; i < 6; i++)
    pdcch_pdu->FreqDomainResource[i] = coreset->frequencyDomainResources.buf[i];

  LOG_D(NR_MAC,
        "Coreset : BWPstart %d, BWPsize %d, SCS %d, freq %x, , duration %ld\n",
        pdcch_pdu->BWPStart,
        pdcch_pdu->BWPSize,
        pdcch_pdu->SubcarrierSpacing,
        coreset->frequencyDomainResources.buf[0],
        coreset->duration);

  pdcch_pdu->CceRegMappingType = pdcch->CceRegMappingType;
  pdcch_pdu->RegBundleSize = pdcch->RegBundleSize;
  pdcch_pdu->InterleaverSize = pdcch->InterleaverSize;
  pdcch_pdu->ShiftIndex = pdcch->ShiftIndex;

  pdcch_pdu->CoreSetType = coreset->controlResourceSetId != 0 ? NFAPI_NR_CSET_CONFIG_PDCCH_CONFIG : NFAPI_NR_CSET_CONFIG_MIB_SIB1;

  //precoderGranularity
  pdcch_pdu->precoderGranularity = coreset->precoderGranularity;
}

int nr_get_pucch_resource(NR_ControlResourceSet_t *coreset,
                          NR_PUCCH_Config_t *pucch_Config,
                          int CCEIndex) {
  int r_pucch = -1;
  if(pucch_Config == NULL) {
    int n_rb,rb_offset;
    get_coreset_rballoc(coreset->frequencyDomainResources.buf,&n_rb,&rb_offset);
    const uint16_t N_cce = n_rb * coreset->duration / NR_NB_REG_PER_CCE;
    const int delta_PRI=0;
    r_pucch = ((CCEIndex<<1)/N_cce)+(delta_PRI<<1);
  }
  return r_pucch;
}

// This function configures pucch pdu fapi structure
void nr_configure_pucch(nfapi_nr_pucch_pdu_t *pucch_pdu,
                        NR_ServingCellConfigCommon_t *scc,
                        NR_UE_info_t *UE,
                        uint8_t pucch_resource,
                        uint16_t O_csi,
                        uint16_t O_ack,
                        uint8_t O_sr,
                        int r_pucch)
{
  NR_PUCCH_Resource_t *pucchres;
  NR_PUCCH_FormatConfig_t *pucchfmt;
  NR_UE_UL_BWP_t *current_BWP = &UE->current_UL_BWP;

  int res_found = 0;

  pucch_pdu->bit_len_harq = O_ack;
  pucch_pdu->bit_len_csi_part1 = O_csi;

  uint16_t O_uci = O_csi + O_ack;

  NR_PUSCH_Config_t *pusch_Config = current_BWP->pusch_Config;

  long *pusch_id = pusch_Config ? pusch_Config->dataScramblingIdentityPUSCH : NULL;

  long *id0 = NULL;
  if (pusch_Config &&
      pusch_Config->dmrs_UplinkForPUSCH_MappingTypeA != NULL &&
      pusch_Config->dmrs_UplinkForPUSCH_MappingTypeA->choice.setup->transformPrecodingDisabled != NULL)
    id0 = pusch_Config->dmrs_UplinkForPUSCH_MappingTypeA->choice.setup->transformPrecodingDisabled->scramblingID0;
  else if (pusch_Config && pusch_Config->dmrs_UplinkForPUSCH_MappingTypeB != NULL &&
           pusch_Config->dmrs_UplinkForPUSCH_MappingTypeB->choice.setup->transformPrecodingDisabled != NULL)
    id0 = pusch_Config->dmrs_UplinkForPUSCH_MappingTypeB->choice.setup->transformPrecodingDisabled->scramblingID0;
  else id0 = scc->physCellId;

  NR_PUCCH_ConfigCommon_t *pucch_ConfigCommon = current_BWP->pucch_ConfigCommon;

  // hop flags and hopping id are valid for any BWP
  switch (pucch_ConfigCommon->pucch_GroupHopping){
  case 0 :
    // if neither, both disabled
    pucch_pdu->group_hop_flag = 0;
    pucch_pdu->sequence_hop_flag = 0;
    break;
  case 1 :
    // if enable, group enabled
    pucch_pdu->group_hop_flag = 1;
    pucch_pdu->sequence_hop_flag = 0;
    break;
  case 2 :
    // if disable, sequence disabled
    pucch_pdu->group_hop_flag = 0;
    pucch_pdu->sequence_hop_flag = 1;
    break;
  default:
    AssertFatal(1==0,"Group hopping flag %ld undefined (0,1,2) \n", pucch_ConfigCommon->pucch_GroupHopping);
  }

  if (pucch_ConfigCommon->hoppingId != NULL)
    pucch_pdu->hopping_id = *pucch_ConfigCommon->hoppingId;
  else
    pucch_pdu->hopping_id = *scc->physCellId;

  pucch_pdu->bwp_size  = current_BWP->BWPSize;
  pucch_pdu->bwp_start = current_BWP->BWPStart;
  pucch_pdu->subcarrier_spacing = current_BWP->scs;
  pucch_pdu->cyclic_prefix = (current_BWP->cyclicprefix==NULL) ? 0 : *current_BWP->cyclicprefix;

  NR_PUCCH_Config_t *pucch_Config = current_BWP->pucch_Config;
  if (r_pucch < 0 || pucch_Config) {
    LOG_D(NR_MAC, "pucch_acknak: Filling dedicated configuration for PUCCH\n");

    int resource_id = get_pucch_resourceid(pucch_Config, O_uci, pucch_resource);

    AssertFatal(pucch_Config->resourceToAddModList!=NULL,
                "PUCCH resourceToAddModList is null\n");

    int n_list = pucch_Config->resourceToAddModList->list.count;
    AssertFatal(n_list > 0, "PUCCH resourceToAddModList is empty\n");

    // going through the list of PUCCH resources to find the one indexed by resource_id
    for (int i = 0; i < n_list; i++) {
      pucchres = pucch_Config->resourceToAddModList->list.array[i];
      if (pucchres->pucch_ResourceId == resource_id) {
        res_found = 1;
        pucch_pdu->prb_start = pucchres->startingPRB;
        pucch_pdu->rnti = UE->rnti;
        // FIXME why there is only one frequency hopping flag
        // what about inter slot frequency hopping?
        pucch_pdu->freq_hop_flag = pucchres->intraSlotFrequencyHopping ? 1 : 0;
        pucch_pdu->second_hop_prb = pucchres->secondHopPRB!= NULL ?  *pucchres->secondHopPRB : 0;
        switch(pucchres->format.present) {
          case NR_PUCCH_Resource__format_PR_format0 :
            pucch_pdu->format_type = 0;
            pucch_pdu->initial_cyclic_shift = pucchres->format.choice.format0->initialCyclicShift;
            pucch_pdu->nr_of_symbols = pucchres->format.choice.format0->nrofSymbols;
            pucch_pdu->start_symbol_index = pucchres->format.choice.format0->startingSymbolIndex;
            pucch_pdu->sr_flag = O_sr;
            pucch_pdu->prb_size = 1;
            break;
          case NR_PUCCH_Resource__format_PR_format1 :
            pucch_pdu->format_type = 1;
            pucch_pdu->initial_cyclic_shift = pucchres->format.choice.format1->initialCyclicShift;
            pucch_pdu->nr_of_symbols = pucchres->format.choice.format1->nrofSymbols;
            pucch_pdu->start_symbol_index = pucchres->format.choice.format1->startingSymbolIndex;
            pucch_pdu->time_domain_occ_idx = pucchres->format.choice.format1->timeDomainOCC;
            pucch_pdu->sr_flag = O_sr;
            pucch_pdu->prb_size = 1;
            break;
          case NR_PUCCH_Resource__format_PR_format2 :
            pucch_pdu->format_type = 2;
            pucch_pdu->sr_flag = O_sr;
            pucch_pdu->nr_of_symbols = pucchres->format.choice.format2->nrofSymbols;
            pucch_pdu->start_symbol_index = pucchres->format.choice.format2->startingSymbolIndex;
            pucch_pdu->data_scrambling_id = pusch_id ? *pusch_id : *scc->physCellId;
            pucch_pdu->dmrs_scrambling_id = id0 ? *id0 : *scc->physCellId;
            pucch_pdu->prb_size = compute_pucch_prb_size(2,
                                                         pucchres->format.choice.format2->nrofPRBs,
                                                         O_csi,
                                                         O_ack,
                                                         O_sr,
                                                         pucch_Config->format2->choice.setup->maxCodeRate,
                                                         2,
                                                         pucchres->format.choice.format2->nrofSymbols,
                                                         8);
            pucch_pdu->bit_len_csi_part1 = O_csi;
            break;
          case NR_PUCCH_Resource__format_PR_format3 :
            pucch_pdu->format_type = 3;
            pucch_pdu->nr_of_symbols = pucchres->format.choice.format3->nrofSymbols;
            pucch_pdu->start_symbol_index = pucchres->format.choice.format3->startingSymbolIndex;
            pucch_pdu->data_scrambling_id = pusch_id ? *pusch_id : *scc->physCellId;
            if (pucch_Config->format3 == NULL) {
              pucch_pdu->pi_2bpsk = 0;
              pucch_pdu->add_dmrs_flag = 0;
            }
            else {
              pucchfmt = pucch_Config->format3->choice.setup;
              pucch_pdu->pi_2bpsk = pucchfmt->pi2BPSK ? 1 : 0;
              pucch_pdu->add_dmrs_flag = pucchfmt->additionalDMRS ? 1 : 0;
            }
            int f3_dmrs_symbols = get_f3_dmrs_symbols(pucchres, pucch_Config);
            pucch_pdu->prb_size = compute_pucch_prb_size(3,
                                                         pucchres->format.choice.format3->nrofPRBs,
                                                         O_csi,
                                                         O_ack,
                                                         O_sr,
                                                         pucch_Config->format3->choice.setup->maxCodeRate,
                                                         2 - pucch_pdu->pi_2bpsk,
                                                         pucchres->format.choice.format3->nrofSymbols - f3_dmrs_symbols,
                                                         12);
            pucch_pdu->bit_len_csi_part1 = O_csi;
            break;
          case NR_PUCCH_Resource__format_PR_format4 :
            pucch_pdu->format_type = 4;
            pucch_pdu->nr_of_symbols = pucchres->format.choice.format4->nrofSymbols;
            pucch_pdu->start_symbol_index = pucchres->format.choice.format4->startingSymbolIndex;
            pucch_pdu->pre_dft_occ_len = pucchres->format.choice.format4->occ_Length;
            pucch_pdu->pre_dft_occ_idx = pucchres->format.choice.format4->occ_Index;
            pucch_pdu->data_scrambling_id = pusch_id!= NULL ? *pusch_id : *scc->physCellId;
            if (pucch_Config->format3 == NULL) {
              pucch_pdu->pi_2bpsk = 0;
              pucch_pdu->add_dmrs_flag = 0;
            }
            else {
              pucchfmt = pucch_Config->format3->choice.setup;
              pucch_pdu->pi_2bpsk = pucchfmt->pi2BPSK != NULL;
              pucch_pdu->add_dmrs_flag = pucchfmt->additionalDMRS != NULL;
            }
            pucch_pdu->bit_len_csi_part1 = O_csi;
            break;
          default :
            AssertFatal(1==0,"Undefined PUCCH format \n");
        }
      }
    }
    AssertFatal(res_found == 1, "No PUCCH resource found corresponding to id %d\n", resource_id);
    LOG_D(NR_MAC,
          "Configure pucch: pucch_pdu->format_type %d pucch_pdu->bit_len_harq %d, pucch->pdu->bit_len_csi %d\n",
          pucch_pdu->format_type,
          pucch_pdu->bit_len_harq,
          pucch_pdu->bit_len_csi_part1);
  } else { // this is the default PUCCH configuration, PUCCH format 0 or 1
    LOG_D(NR_MAC,
          "pucch_acknak: Filling default PUCCH configuration from Tables (r_pucch %d, pucch_Config %p)\n",
          r_pucch,
          pucch_Config);
    int rsetindex = *pucch_ConfigCommon->pucch_ResourceCommon;
    int prb_start, second_hop_prb, nr_of_symb, start_symb;
    set_r_pucch_parms(rsetindex,
                      r_pucch,
                      pucch_pdu->bwp_size,
                      &prb_start,
                      &second_hop_prb,
                      &nr_of_symb,
                      &start_symb);

    pucch_pdu->prb_start = prb_start;
    pucch_pdu->rnti = UE->rnti;
    pucch_pdu->freq_hop_flag = 1;
    pucch_pdu->second_hop_prb = second_hop_prb;
    pucch_pdu->format_type = default_pucch_fmt[rsetindex];
    pucch_pdu->initial_cyclic_shift = r_pucch%default_pucch_csset[rsetindex];
    if (rsetindex==3||rsetindex==7||rsetindex==11) pucch_pdu->initial_cyclic_shift*=6;
    else if (rsetindex==1||rsetindex==2) pucch_pdu->initial_cyclic_shift*=4;
    else pucch_pdu->initial_cyclic_shift*=3;
    pucch_pdu->nr_of_symbols = nr_of_symb;
    pucch_pdu->start_symbol_index = start_symb;
    if (pucch_pdu->format_type == 1) pucch_pdu->time_domain_occ_idx = 0; // check this!!
    pucch_pdu->sr_flag = O_sr;
    pucch_pdu->prb_size=1;
  }
  // Beamforming
  pucch_pdu->beamforming.num_prgs = 1;
  pucch_pdu->beamforming.prg_size = pucch_pdu->prb_size;
  pucch_pdu->beamforming.dig_bf_interface = 1;
  pucch_pdu->beamforming.prgs_list[0].dig_bf_interface_list[0].beam_idx = UE->UE_beam_index;
}

void set_r_pucch_parms(int rsetindex,
                       int r_pucch,
                       int bwp_size,
                       int *prb_start,
                       int *second_hop_prb,
                       int *nr_of_symbols,
                       int *start_symbol_index) {

  // procedure described in 38.213 section 9.2.1

  int prboffset = r_pucch/default_pucch_csset[rsetindex];
  int prboffsetm8 = (r_pucch-8)/default_pucch_csset[rsetindex];

  *prb_start = (r_pucch>>3)==0 ?
              default_pucch_prboffset[rsetindex] + prboffset:
              bwp_size-1-default_pucch_prboffset[rsetindex]-prboffsetm8;

  *second_hop_prb = (r_pucch>>3)==0?
                   bwp_size-1-default_pucch_prboffset[rsetindex]-prboffset:
                   default_pucch_prboffset[rsetindex] + prboffsetm8;

  *nr_of_symbols = default_pucch_numbsymb[rsetindex];
  *start_symbol_index = default_pucch_firstsymb[rsetindex];
}

static void prepare_dci_X1(const NR_UE_ServingCell_Info_t *servingCellInfo,
                           const NR_UE_DL_BWP_t *current_BWP,
                           const NR_ControlResourceSet_t *coreset,
                           dci_pdu_rel15_t *dci_pdu_rel15,
                           nr_dci_format_t format)
{
  const NR_PDSCH_Config_t *pdsch_Config = current_BWP ? current_BWP->pdsch_Config : NULL;

  switch(format) {
    case NR_UL_DCI_FORMAT_0_1:
      // format indicator
      dci_pdu_rel15->format_indicator = 0;
      // carrier indicator
      if (servingCellInfo->crossCarrierSchedulingConfig != NULL)
        AssertFatal(1==0,"Cross Carrier Scheduling Config currently not supported\n");
      // supplementary uplink
      if (servingCellInfo->supplementaryUplink != NULL)
        AssertFatal(1==0,"Supplementary Uplink currently not supported\n");
      // SRS request
      dci_pdu_rel15->srs_request.val = 0;
      dci_pdu_rel15->ulsch_indicator = 1;
      break;
    case NR_DL_DCI_FORMAT_1_1:
      // format indicator
      dci_pdu_rel15->format_indicator = 1;
      // carrier indicator
      if (servingCellInfo->crossCarrierSchedulingConfig != NULL)
        AssertFatal(1==0,"Cross Carrier Scheduling Config currently not supported\n");
      //vrb to prb mapping
      if (pdsch_Config->vrb_ToPRB_Interleaver==NULL)
        dci_pdu_rel15->vrb_to_prb_mapping.val = 0;
      else
        dci_pdu_rel15->vrb_to_prb_mapping.val = 1;
      //bundling size indicator
      if (pdsch_Config->prb_BundlingType.present == NR_PDSCH_Config__prb_BundlingType_PR_dynamicBundling)
        AssertFatal(1==0,"Dynamic PRB bundling type currently not supported\n");
      //rate matching indicator
      uint16_t msb = (pdsch_Config->rateMatchPatternGroup1==NULL)?0:1;
      uint16_t lsb = (pdsch_Config->rateMatchPatternGroup2==NULL)?0:1;
      dci_pdu_rel15->rate_matching_indicator.val = lsb | (msb<<1);
      // aperiodic ZP CSI-RS trigger
      if (pdsch_Config->aperiodic_ZP_CSI_RS_ResourceSetsToAddModList != NULL)
        AssertFatal(1==0,"Aperiodic ZP CSI-RS currently not supported\n");
      // transmission configuration indication
      if (coreset->tci_PresentInDCI != NULL)
        AssertFatal(1==0,"TCI in DCI currently not supported\n");
      //srs resource set
      if (servingCellInfo->carrierSwitching) {
        if (servingCellInfo->carrierSwitching->srs_TPC_PDCCH_Group) {
          switch (servingCellInfo->carrierSwitching->srs_TPC_PDCCH_Group->present) {
            case NR_SRS_CarrierSwitching__srs_TPC_PDCCH_Group_PR_NOTHING:
              dci_pdu_rel15->srs_request.val = 0;
              break;
            case NR_SRS_CarrierSwitching__srs_TPC_PDCCH_Group_PR_typeA:
              AssertFatal(1==0,"SRS TPC PRCCH group type A currently not supported\n");
              break;
            case NR_SRS_CarrierSwitching__srs_TPC_PDCCH_Group_PR_typeB:
              AssertFatal(1==0,"SRS TPC PRCCH group type B currently not supported\n");
              break;
          }
        } else
          dci_pdu_rel15->srs_request.val = 0;
      } else
        dci_pdu_rel15->srs_request.val = 0;
    // CBGTI and CBGFI
      if (servingCellInfo->pdsch_CGB_Transmission)
        AssertFatal(1 == 0, "CBG transmission currently not supported\n");
      break;
  default :
    AssertFatal(1==0,"Prepare dci currently only implemented for 1_1 and 0_1 \n");
  }
}

void fill_dci_pdu_rel15(const NR_UE_ServingCell_Info_t *servingCellInfo,
                        const NR_UE_DL_BWP_t *current_DL_BWP,
                        const NR_UE_UL_BWP_t *current_UL_BWP,
                        nfapi_nr_dl_dci_pdu_t *pdcch_dci_pdu,
                        dci_pdu_rel15_t *dci_pdu_rel15,
                        int dci_format,
                        int rnti_type,
                        int bwp_id,
                        NR_SearchSpace_t *ss,
                        NR_ControlResourceSet_t *coreset,
                        long pdsch_HARQ_ACK_Codebook,
                        uint16_t cset0_bwp_size)
{
  uint8_t fsize = 0, pos = 0;
  uint64_t *dci_pdu = (uint64_t *)pdcch_dci_pdu->Payload;
  *dci_pdu = 0;
  uint16_t alt_size = 0;
  uint16_t N_RB;
  if(current_DL_BWP) {
    N_RB = get_rb_bwp_dci(dci_format,
                          ss->searchSpaceType->present,
                          cset0_bwp_size,
                          current_UL_BWP->BWPSize,
                          current_DL_BWP->BWPSize,
                          servingCellInfo->initial_ul_BWPSize,
                          servingCellInfo->initial_dl_BWPSize);

    // computing alternative size for padding
    dci_pdu_rel15_t temp_pdu;
    if(dci_format == NR_DL_DCI_FORMAT_1_0)
      alt_size = nr_dci_size(current_DL_BWP,
                             current_UL_BWP,
                             servingCellInfo,
                             pdsch_HARQ_ACK_Codebook,
                             &temp_pdu,
                             NR_UL_DCI_FORMAT_0_0,
                             rnti_type,
                             coreset,
                             bwp_id,
                             ss->searchSpaceType->present,
                             cset0_bwp_size,
                             0);

    if(dci_format == NR_UL_DCI_FORMAT_0_0)
      alt_size = nr_dci_size(current_DL_BWP,
                             current_UL_BWP,
                             servingCellInfo,
                             pdsch_HARQ_ACK_Codebook,
                             &temp_pdu,
                             NR_DL_DCI_FORMAT_1_0,
                             rnti_type,
                             coreset,
                             bwp_id,
                             ss->searchSpaceType->present,
                             cset0_bwp_size,
                             0);

  }
  else
    N_RB = cset0_bwp_size;

  int dci_size = nr_dci_size(current_DL_BWP,
                             current_UL_BWP,
                             servingCellInfo,
                             pdsch_HARQ_ACK_Codebook,
                             dci_pdu_rel15,
                             dci_format,
                             rnti_type,
                             coreset,
                             bwp_id,
                             ss->searchSpaceType->present,
                             cset0_bwp_size,
                             alt_size);
  if (dci_size == 0)
    return;
  pdcch_dci_pdu->PayloadSizeBits = dci_size;
  AssertFatal(dci_size <= 64, "DCI sizes above 64 bits not yet supported");
  if (dci_format == NR_DL_DCI_FORMAT_1_1 || dci_format == NR_UL_DCI_FORMAT_0_1)
    prepare_dci_X1(servingCellInfo, current_DL_BWP, coreset, dci_pdu_rel15, dci_format);

  /// Payload generation
  switch (dci_format) {
  case NR_DL_DCI_FORMAT_1_0:
    switch (rnti_type) {
      case TYPE_RA_RNTI_:
        // Freq domain assignment
        fsize = (int)ceil(log2((N_RB * (N_RB + 1)) >> 1));
        pos = fsize;
        *dci_pdu |= (((uint64_t)dci_pdu_rel15->frequency_domain_assignment.val & ((1 << fsize) - 1)) << (dci_size - pos));
        LOG_D(NR_MAC,
              "RA_RNTI, size %d frequency-domain assignment %d (%d bits) N_RB_BWP %d=> %d (0x%lx)\n",
              dci_size,
              dci_pdu_rel15->frequency_domain_assignment.val,
              fsize,
              N_RB,
              dci_size - pos,
              *dci_pdu);
        // Time domain assignment
        pos += 4;
        *dci_pdu |= (((uint64_t)dci_pdu_rel15->time_domain_assignment.val & 0xf) << (dci_size - pos));
        LOG_D(NR_MAC,
              "time-domain assignment %d  (4 bits)=> %d (0x%lx)\n",
              dci_pdu_rel15->time_domain_assignment.val,
              dci_size - pos,
              *dci_pdu);
        // VRB to PRB mapping
        pos++;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->vrb_to_prb_mapping.val & 0x1) << (dci_size - pos);
        LOG_D(NR_MAC,
              "vrb to prb mapping %d  (1 bits)=> %d (0x%lx)\n",
              dci_pdu_rel15->vrb_to_prb_mapping.val,
              dci_size - pos,
              *dci_pdu);
        // MCS
        pos += 5;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->mcs & 0x1f) << (dci_size - pos);
        LOG_D(NR_MAC, "mcs %d  (5 bits)=> %d (0x%lx)\n", dci_pdu_rel15->mcs, dci_size - pos, *dci_pdu);
        // TB scaling
        pos += 2;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->tb_scaling & 0x3) << (dci_size - pos);
        LOG_D(NR_MAC, "tb_scaling %d  (2 bits)=> %d (0x%lx)\n", dci_pdu_rel15->tb_scaling, dci_size - pos, *dci_pdu);
        break;

      case TYPE_C_RNTI_:
        // indicating a DL DCI format 1bit
        pos++;
        *dci_pdu |= ((uint64_t)1) << (dci_size - pos);
        LOG_D(NR_MAC,
              "DCI1_0 (size %d): Format indicator %d (%d bits) N_RB_BWP %d => %d (0x%lx)\n",
              dci_size,
              dci_pdu_rel15->format_indicator,
              1,
              N_RB,
              dci_size - pos,
              *dci_pdu);
        // Freq domain assignment (275rb >> fsize = 16)
        fsize = (int)ceil(log2((N_RB * (N_RB + 1)) >> 1));
        pos += fsize;
        *dci_pdu |= (((uint64_t)dci_pdu_rel15->frequency_domain_assignment.val & ((1 << fsize) - 1)) << (dci_size - pos));
        LOG_D(NR_MAC,
              "Freq domain assignment %d (%d bits)=> %d (0x%lx)\n",
              dci_pdu_rel15->frequency_domain_assignment.val,
              fsize,
              dci_size - pos,
              *dci_pdu);
        uint16_t is_ra = 1;
        for (int i = 0; i < fsize; i++) {
          if (!((dci_pdu_rel15->frequency_domain_assignment.val >> i) & 1)) {
            is_ra = 0;
            break;
          }
        }
        if (is_ra) { // fsize are all 1  38.212 p86
          // ra_preamble_index 6 bits
          pos += 6;
          *dci_pdu |= ((dci_pdu_rel15->ra_preamble_index & 0x3f) << (dci_size - pos));
          // UL/SUL indicator  1 bit
          pos++;
          *dci_pdu |= (dci_pdu_rel15->ul_sul_indicator.val & 1) << (dci_size - pos);
          // SS/PBCH index  6 bits
          pos += 6;
          *dci_pdu |= ((dci_pdu_rel15->ss_pbch_index & 0x3f) << (dci_size - pos));
          //  prach_mask_index  4 bits
          pos += 4;
          *dci_pdu |= ((dci_pdu_rel15->prach_mask_index & 0xf) << (dci_size - pos));
        } else {
          // Time domain assignment 4bit
          pos += 4;
          *dci_pdu |= ((dci_pdu_rel15->time_domain_assignment.val & 0xf) << (dci_size - pos));
          LOG_D(NR_MAC,
                "Time domain assignment %d (%d bits)=> %d (0x%lx)\n",
                dci_pdu_rel15->time_domain_assignment.val,
                4,
                dci_size - pos,
                *dci_pdu);
          // VRB to PRB mapping  1bit
          pos++;
          *dci_pdu |= (dci_pdu_rel15->vrb_to_prb_mapping.val & 1) << (dci_size - pos);
          LOG_D(NR_MAC,
                "VRB to PRB %d (%d bits)=> %d (0x%lx)\n",
                dci_pdu_rel15->vrb_to_prb_mapping.val,
                1,
                dci_size - pos,
                *dci_pdu);
          // MCS 5bit  //bit over 32, so dci_pdu ++
          pos += 5;
          *dci_pdu |= (dci_pdu_rel15->mcs & 0x1f) << (dci_size - pos);
          LOG_D(NR_MAC, "MCS %d (%d bits)=> %d (0x%lx)\n", dci_pdu_rel15->mcs, 5, dci_size - pos, *dci_pdu);
          // New data indicator 1bit
          pos++;
          *dci_pdu |= (dci_pdu_rel15->ndi & 1) << (dci_size - pos);
          LOG_D(NR_MAC, "NDI %d (%d bits)=> %d (0x%lx)\n", dci_pdu_rel15->ndi, 1, dci_size - pos, *dci_pdu);
          // Redundancy version  2bit
          pos += 2;
          *dci_pdu |= (dci_pdu_rel15->rv & 0x3) << (dci_size - pos);
          LOG_D(NR_MAC, "RV %d (%d bits)=> %d (0x%lx)\n", dci_pdu_rel15->rv, 2, dci_size - pos, *dci_pdu);
          // HARQ process number  4bit/5bit
          fsize = dci_pdu_rel15->harq_pid.nbits;
          pos += fsize;
          *dci_pdu |= ((dci_pdu_rel15->harq_pid.val & ((1 << fsize) - 1)) << (dci_size - pos));
          LOG_D(NR_MAC, "HARQ_PID %d (%d bits)=> %d (0x%lx)\n", dci_pdu_rel15->harq_pid.val, fsize, dci_size - pos, *dci_pdu);
          // Downlink assignment index  2bit
          pos += 2;
          *dci_pdu |= ((dci_pdu_rel15->dai[0].val & 3) << (dci_size - pos));
          LOG_D(NR_MAC, "DAI %d (%d bits)=> %d (0x%lx)\n", dci_pdu_rel15->dai[0].val, 2, dci_size - pos, *dci_pdu);
          // TPC command for scheduled PUCCH  2bit
          pos += 2;
          *dci_pdu |= ((dci_pdu_rel15->tpc & 3) << (dci_size - pos));
          LOG_D(NR_MAC, "TPC %d (%d bits)=> %d (0x%lx)\n", dci_pdu_rel15->tpc, 2, dci_size - pos, *dci_pdu);
          // PUCCH resource indicator  3bit
          pos += 3;
          *dci_pdu |= ((dci_pdu_rel15->pucch_resource_indicator & 0x7) << (dci_size - pos));
          LOG_D(NR_MAC,
                "PUCCH RI %d (%d bits)=> %d (0x%lx)\n",
                dci_pdu_rel15->pucch_resource_indicator,
                3,
                dci_size - pos,
                *dci_pdu);
          // PDSCH-to-HARQ_feedback timing indicator 3bit
          pos += 3;
          *dci_pdu |= ((dci_pdu_rel15->pdsch_to_harq_feedback_timing_indicator.val & 0x7) << (dci_size - pos));
          LOG_D(NR_MAC,
                "PDSCH to HARQ TI %d (%d bits)=> %d (0x%lx)\n",
                dci_pdu_rel15->pdsch_to_harq_feedback_timing_indicator.val,
                3,
                dci_size - pos,
                *dci_pdu);
        } // end else
        break;

      case TYPE_P_RNTI_:
        // Short Messages Indicator – 2 bits
        for (int i = 0; i < 2; i++)
          *dci_pdu |= (((uint64_t)dci_pdu_rel15->short_messages_indicator >> (1 - i)) & 1) << (dci_size - pos++);
        // Short Messages – 8 bits
        for (int i = 0; i < 8; i++)
          *dci_pdu |= (((uint64_t)dci_pdu_rel15->short_messages >> (7 - i)) & 1) << (dci_size - pos++);
        // Freq domain assignment 0-16 bit
        fsize = (int)ceil(log2((N_RB * (N_RB + 1)) >> 1));
        for (int i = 0; i < fsize; i++)
          *dci_pdu |= (((uint64_t)dci_pdu_rel15->frequency_domain_assignment.val >> (fsize - i - 1)) & 1) << (dci_size - pos++);
        // Time domain assignment 4 bit
        for (int i = 0; i < 4; i++)
          *dci_pdu |= (((uint64_t)dci_pdu_rel15->time_domain_assignment.val >> (3 - i)) & 1) << (dci_size - pos++);
        // VRB to PRB mapping 1 bit
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->vrb_to_prb_mapping.val & 1) << (dci_size - pos++);
        // MCS 5 bit
        for (int i = 0; i < 5; i++)
          *dci_pdu |= (((uint64_t)dci_pdu_rel15->mcs >> (4 - i)) & 1) << (dci_size - pos++);
        // TB scaling 2 bit
        for (int i = 0; i < 2; i++)
          *dci_pdu |= (((uint64_t)dci_pdu_rel15->tb_scaling >> (1 - i)) & 1) << (dci_size - pos++);
        break;

      case TYPE_SI_RNTI_:
        pos = 1;
        // Freq domain assignment 0-16 bit
        fsize = (int)ceil(log2((N_RB * (N_RB + 1)) >> 1));
        LOG_D(NR_MAC, "fsize = %i\n", fsize);
        for (int i = 0; i < fsize; i++)
          *dci_pdu |= (((uint64_t)dci_pdu_rel15->frequency_domain_assignment.val >> (fsize - i - 1)) & 1) << (dci_size - pos++);
        LOG_D(NR_MAC, "dci_pdu_rel15->frequency_domain_assignment.val = %i\n", dci_pdu_rel15->frequency_domain_assignment.val);
        // Time domain assignment 4 bit
        for (int i = 0; i < 4; i++)
          *dci_pdu |= (((uint64_t)dci_pdu_rel15->time_domain_assignment.val >> (3 - i)) & 1) << (dci_size - pos++);
        LOG_D(NR_MAC, "dci_pdu_rel15->time_domain_assignment.val = %i\n", dci_pdu_rel15->time_domain_assignment.val);
        // VRB to PRB mapping 1 bit
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->vrb_to_prb_mapping.val & 1) << (dci_size - pos++);
        LOG_D(NR_MAC, "dci_pdu_rel15->vrb_to_prb_mapping.val = %i\n", dci_pdu_rel15->vrb_to_prb_mapping.val);
        // MCS 5bit  //bit over 32, so dci_pdu ++
        for (int i = 0; i < 5; i++)
          *dci_pdu |= (((uint64_t)dci_pdu_rel15->mcs >> (4 - i)) & 1) << (dci_size - pos++);
        LOG_D(NR_MAC, "dci_pdu_rel15->mcs = %i\n", dci_pdu_rel15->mcs);
        // Redundancy version  2bit
        for (int i = 0; i < 2; i++)
          *dci_pdu |= (((uint64_t)dci_pdu_rel15->rv >> (1 - i)) & 1) << (dci_size - pos++);
        LOG_D(NR_MAC, "dci_pdu_rel15->rv = %i\n", dci_pdu_rel15->rv);
        // System information indicator 1bit
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->system_info_indicator & 1) << (dci_size - pos++);
        LOG_D(NR_MAC, "dci_pdu_rel15->system_info_indicator = %i\n", dci_pdu_rel15->system_info_indicator);
        break;

      case TYPE_TC_RNTI_:
        pos = 1;
        // indicating a DL DCI format 1bit
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->format_indicator & 1) << (dci_size - pos++);
        // Freq domain assignment 0-16 bit
        fsize = (int)ceil(log2((N_RB * (N_RB + 1)) >> 1));
        for (int i = 0; i < fsize; i++)
          *dci_pdu |= (((uint64_t)dci_pdu_rel15->frequency_domain_assignment.val >> (fsize - i - 1)) & 1) << (dci_size - pos++);
        // Time domain assignment 4 bit
        for (int i = 0; i < 4; i++)
          *dci_pdu |= (((uint64_t)dci_pdu_rel15->time_domain_assignment.val >> (3 - i)) & 1) << (dci_size - pos++);
        // VRB to PRB mapping 1 bit
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->vrb_to_prb_mapping.val & 1) << (dci_size - pos++);
        // MCS 5bit  //bit over 32, so dci_pdu ++
        for (int i = 0; i < 5; i++)
          *dci_pdu |= (((uint64_t)dci_pdu_rel15->mcs >> (4 - i)) & 1) << (dci_size - pos++);
        // New data indicator 1bit
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->ndi & 1) << (dci_size - pos++);
        // Redundancy version  2bit
        for (int i = 0; i < 2; i++)
          *dci_pdu |= (((uint64_t)dci_pdu_rel15->rv >> (1 - i)) & 1) << (dci_size - pos++);
        // HARQ process number  4bit/5bit
        fsize = dci_pdu_rel15->harq_pid.nbits;
        for (int i = 0; i < fsize; i++)
          *dci_pdu |= (((uint64_t)dci_pdu_rel15->harq_pid.val >> (fsize - i - 1)) & 1) << (dci_size - pos++);
        // Downlink assignment index – 2 bits
        for (int i = 0; i < 2; i++)
          *dci_pdu |= (((uint64_t)dci_pdu_rel15->dai[0].val >> (1 - i)) & 1) << (dci_size - pos++);
        // TPC command for scheduled PUCCH – 2 bits
        for (int i = 0; i < 2; i++)
          *dci_pdu |= (((uint64_t)dci_pdu_rel15->tpc >> (1 - i)) & 1) << (dci_size - pos++);
        // PUCCH resource indicator – 3 bits
        for (int i = 0; i < 3; i++)
          *dci_pdu |= (((uint64_t)dci_pdu_rel15->pucch_resource_indicator >> (2 - i)) & 1) << (dci_size - pos++);
        // PDSCH-to-HARQ_feedback timing indicator – 3 bits
        for (int i = 0; i < 3; i++)
          *dci_pdu |= (((uint64_t)dci_pdu_rel15->pdsch_to_harq_feedback_timing_indicator.val >> (2 - i)) & 1) << (dci_size - pos++);

        LOG_D(NR_MAC, "N_RB = %i\n", N_RB);
        LOG_D(NR_MAC, "dci_size = %i\n", dci_size);
        LOG_D(NR_MAC, "fsize = %i\n", fsize);
        LOG_D(NR_MAC, "dci_pdu_rel15->format_indicator = %i\n", dci_pdu_rel15->format_indicator);
        LOG_D(NR_MAC, "dci_pdu_rel15->frequency_domain_assignment.val = %i\n", dci_pdu_rel15->frequency_domain_assignment.val);
        LOG_D(NR_MAC, "dci_pdu_rel15->time_domain_assignment.val = %i\n", dci_pdu_rel15->time_domain_assignment.val);
        LOG_D(NR_MAC, "dci_pdu_rel15->vrb_to_prb_mapping.val = %i\n", dci_pdu_rel15->vrb_to_prb_mapping.val);
        LOG_D(NR_MAC, "dci_pdu_rel15->mcs = %i\n", dci_pdu_rel15->mcs);
        LOG_D(NR_MAC, "dci_pdu_rel15->rv = %i\n", dci_pdu_rel15->rv);
        LOG_D(NR_MAC, "dci_pdu_rel15->harq_pid = %i\n", dci_pdu_rel15->harq_pid.val);
        LOG_D(NR_MAC, "dci_pdu_rel15->dai[0].val = %i\n", dci_pdu_rel15->dai[0].val);
        LOG_D(NR_MAC, "dci_pdu_rel15->tpc = %i\n", dci_pdu_rel15->tpc);
        LOG_D(NR_MAC,
              "dci_pdu_rel15->pdsch_to_harq_feedback_timing_indicator.val = %i\n",
              dci_pdu_rel15->pdsch_to_harq_feedback_timing_indicator.val);

        break;
    }
    break;

  case NR_UL_DCI_FORMAT_0_0:
    switch (rnti_type) {
      case TYPE_C_RNTI_:
        LOG_D(NR_MAC,
              "Filling format 0_0 DCI for CRNTI (size %d bits, format ind %d)\n",
              dci_size,
              dci_pdu_rel15->format_indicator);
        // indicating a UL DCI format 1bit
        pos = 1;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->format_indicator & 1) << (dci_size - pos);
        // Freq domain assignment  max 16 bit
        fsize = dci_pdu_rel15->frequency_domain_assignment.nbits;
        pos += fsize;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->frequency_domain_assignment.val & ((1 << fsize) - 1)) << (dci_size - pos);
        // Time domain assignment 4bit
        pos += 4;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->time_domain_assignment.val & ((1 << 4) - 1)) << (dci_size - pos);
        // Frequency hopping flag – 1 bit
        pos++;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->frequency_hopping_flag.val & 1) << (dci_size - pos);
        // MCS  5 bit
        pos += 5;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->mcs & 0x1f) << (dci_size - pos);
        // New data indicator 1bit
        pos++;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->ndi & 1) << (dci_size - pos);
        // Redundancy version  2bit
        pos += 2;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->rv & 0x3) << (dci_size - pos);
        // HARQ process number  4bit/5bit
        fsize = dci_pdu_rel15->harq_pid.nbits;
        pos += fsize;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->harq_pid.val & ((1 << fsize) - 1)) << (dci_size - pos);
        // TPC command for scheduled PUSCH – 2 bits
        pos += 2;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->tpc & 0x3) << (dci_size - pos);
        // Padding bits
        for (int a = pos; a < dci_size; a++)
          *dci_pdu |= ((uint64_t)dci_pdu_rel15->padding & 1) << (dci_size - pos++);
        // UL/SUL indicator – 1 bit
        /* commented for now (RK): need to get this from BWP descriptor
        if (cfg->pucch_config.pucch_GroupHopping.value)
          *dci_pdu |=
        ((uint64_t)dci_pdu_rel15->ul_sul_indicator.val&1)<<(dci_size-pos++);
          */

        LOG_D(NR_MAC, "N_RB = %i\n", N_RB);
        LOG_D(NR_MAC, "dci_size = %i\n", dci_size);
        LOG_D(NR_MAC, "fsize = %i\n", fsize);
        LOG_D(NR_MAC, "dci_pdu_rel15->frequency_domain_assignment.val = %i\n", dci_pdu_rel15->frequency_domain_assignment.val);
        LOG_D(NR_MAC, "dci_pdu_rel15->time_domain_assignment.val = %i\n", dci_pdu_rel15->time_domain_assignment.val);
        LOG_D(NR_MAC, "dci_pdu_rel15->frequency_hopping_flag.val = %i\n", dci_pdu_rel15->frequency_hopping_flag.val);
        LOG_D(NR_MAC, "dci_pdu_rel15->mcs = %i\n", dci_pdu_rel15->mcs);
        LOG_D(NR_MAC, "dci_pdu_rel15->ndi = %i\n", dci_pdu_rel15->ndi);
        LOG_D(NR_MAC, "dci_pdu_rel15->rv = %i\n", dci_pdu_rel15->rv);
        LOG_D(NR_MAC, "dci_pdu_rel15->harq_pid = %i\n", dci_pdu_rel15->harq_pid.val);
        LOG_D(NR_MAC, "dci_pdu_rel15->tpc = %i\n", dci_pdu_rel15->tpc);
        LOG_D(NR_MAC, "dci_pdu_rel15->padding = %i\n", dci_pdu_rel15->padding);
        break;

      case TYPE_TC_RNTI_:
        // indicating a UL DCI format 1bit
        pos = 1;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->format_indicator & 1) << (dci_size - pos);
        // Freq domain assignment  max 16 bit
        fsize = dci_pdu_rel15->frequency_domain_assignment.nbits;
        pos += fsize;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->frequency_domain_assignment.val & ((1 << fsize) - 1)) << (dci_size - pos);
        // Time domain assignment 4bit
        pos += 4;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->time_domain_assignment.val & ((1 << 4) - 1)) << (dci_size - pos);
        // Frequency hopping flag – 1 bit
        pos++;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->frequency_hopping_flag.val & 1) << (dci_size - pos);
        // MCS  5 bit
        pos += 5;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->mcs & 0x1f) << (dci_size - pos);
        // New data indicator 1bit
        pos++;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->ndi & 1) << (dci_size - pos);
        // Redundancy version  2bit
        pos += 2;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->rv & 0x3) << (dci_size - pos);
        // HARQ process number  4bit/5bit
        fsize = dci_pdu_rel15->harq_pid.nbits;
        pos += fsize;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->harq_pid.val & ((1 << fsize) - 1)) << (dci_size - pos);
        // Padding bits
        for (int a = pos; a < dci_size; a++)
          *dci_pdu |= ((uint64_t)dci_pdu_rel15->padding & 1) << (dci_size - pos++);
        // UL/SUL indicator – 1 bit
        /* commented for now (RK): need to get this from BWP descriptor
        if (cfg->pucch_config.pucch_GroupHopping.value)
          *dci_pdu |=
        ((uint64_t)dci_pdu_rel15->ul_sul_indicator.val&1)<<(dci_size-pos++);
          */
        LOG_D(NR_MAC, "N_RB = %i\n", N_RB);
        LOG_D(NR_MAC, "dci_size = %i\n", dci_size);
        LOG_D(NR_MAC, "fsize = %i\n", fsize);
        LOG_D(NR_MAC, "dci_pdu_rel15->frequency_domain_assignment.val = %i\n", dci_pdu_rel15->frequency_domain_assignment.val);
        LOG_D(NR_MAC, "dci_pdu_rel15->time_domain_assignment.val = %i\n", dci_pdu_rel15->time_domain_assignment.val);
        LOG_D(NR_MAC, "dci_pdu_rel15->frequency_hopping_flag.val = %i\n", dci_pdu_rel15->frequency_hopping_flag.val);
        LOG_D(NR_MAC, "dci_pdu_rel15->mcs = %i\n", dci_pdu_rel15->mcs);
        LOG_D(NR_MAC, "dci_pdu_rel15->ndi = %i\n", dci_pdu_rel15->ndi);
        LOG_D(NR_MAC, "dci_pdu_rel15->rv = %i\n", dci_pdu_rel15->rv);
        LOG_D(NR_MAC, "dci_pdu_rel15->harq_pid = %i\n", dci_pdu_rel15->harq_pid.val);
        LOG_D(NR_MAC, "dci_pdu_rel15->tpc = %i\n", dci_pdu_rel15->tpc);
        LOG_D(NR_MAC, "dci_pdu_rel15->padding = %i\n", dci_pdu_rel15->padding);

        break;
    }
    break;

  case NR_UL_DCI_FORMAT_0_1:
    switch (rnti_type) {
      case TYPE_C_RNTI_:
        LOG_D(NR_MAC, "Filling NR_UL_DCI_FORMAT_0_1 size %d format indicator %d\n", dci_size, dci_pdu_rel15->format_indicator);
        // Indicating a DL DCI format 1bit
        pos = 1;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->format_indicator & 0x1) << (dci_size - pos);
        // Carrier indicator
        pos += dci_pdu_rel15->carrier_indicator.nbits;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->carrier_indicator.val & ((1 << dci_pdu_rel15->carrier_indicator.nbits) - 1))
                    << (dci_size - pos);
        // UL/SUL Indicator
        pos += dci_pdu_rel15->ul_sul_indicator.nbits;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->ul_sul_indicator.val & ((1 << dci_pdu_rel15->ul_sul_indicator.nbits) - 1))
                    << (dci_size - pos);
        // BWP indicator
        pos += dci_pdu_rel15->bwp_indicator.nbits;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->bwp_indicator.val & ((1 << dci_pdu_rel15->bwp_indicator.nbits) - 1))
                    << (dci_size - pos);
        // Frequency domain resource assignment
        pos += dci_pdu_rel15->frequency_domain_assignment.nbits;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->frequency_domain_assignment.val
                     & ((1 << dci_pdu_rel15->frequency_domain_assignment.nbits) - 1))
                    << (dci_size - pos);
        // Time domain resource assignment
        pos += dci_pdu_rel15->time_domain_assignment.nbits;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->time_domain_assignment.val & ((1 << dci_pdu_rel15->time_domain_assignment.nbits) - 1))
                    << (dci_size - pos);
        // Frequency hopping
        pos += dci_pdu_rel15->frequency_hopping_flag.nbits;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->frequency_hopping_flag.val & ((1 << dci_pdu_rel15->frequency_hopping_flag.nbits) - 1))
                    << (dci_size - pos);
        // MCS 5bit
        pos += 5;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->mcs & 0x1f) << (dci_size - pos);
        // New data indicator 1bit
        pos += 1;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->ndi & 0x1) << (dci_size - pos);
        // Redundancy version  2bit
        pos += 2;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->rv & 0x3) << (dci_size - pos);
        // HARQ process number  4bit/5bit
        fsize = dci_pdu_rel15->harq_pid.nbits;
        pos += fsize;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->harq_pid.val & ((1 << fsize) - 1)) << (dci_size - pos);
        // 1st Downlink assignment index
        pos += dci_pdu_rel15->dai[0].nbits;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->dai[0].val & ((1 << dci_pdu_rel15->dai[0].nbits) - 1)) << (dci_size - pos);
        // 2nd Downlink assignment index
        pos += dci_pdu_rel15->dai[1].nbits;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->dai[1].val & ((1 << dci_pdu_rel15->dai[1].nbits) - 1)) << (dci_size - pos);
        // TPC command for scheduled PUSCH  2bit
        pos += 2;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->tpc & 0x3) << (dci_size - pos);
        // SRS resource indicator
        pos += dci_pdu_rel15->srs_resource_indicator.nbits;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->srs_resource_indicator.val & ((1 << dci_pdu_rel15->srs_resource_indicator.nbits) - 1))
                    << (dci_size - pos);
        // Precoding info and n. of layers
        pos += dci_pdu_rel15->precoding_information.nbits;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->precoding_information.val & ((1 << dci_pdu_rel15->precoding_information.nbits) - 1))
                    << (dci_size - pos);
        // Antenna ports
        pos += dci_pdu_rel15->antenna_ports.nbits;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->antenna_ports.val & ((1 << dci_pdu_rel15->antenna_ports.nbits) - 1))
                    << (dci_size - pos);
        // SRS request
        pos += dci_pdu_rel15->srs_request.nbits;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->srs_request.val & ((1 << dci_pdu_rel15->srs_request.nbits) - 1)) << (dci_size - pos);
        // CSI request
        pos += dci_pdu_rel15->csi_request.nbits;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->csi_request.val & ((1 << dci_pdu_rel15->csi_request.nbits) - 1)) << (dci_size - pos);
        // CBG transmission information
        pos += dci_pdu_rel15->cbgti.nbits;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->cbgti.val & ((1 << dci_pdu_rel15->cbgti.nbits) - 1)) << (dci_size - pos);
        // PTRS DMRS association
        pos += dci_pdu_rel15->ptrs_dmrs_association.nbits;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->ptrs_dmrs_association.val & ((1 << dci_pdu_rel15->ptrs_dmrs_association.nbits) - 1))
                    << (dci_size - pos);
        // Beta offset indicator
        pos += dci_pdu_rel15->beta_offset_indicator.nbits;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->beta_offset_indicator.val & ((1 << dci_pdu_rel15->beta_offset_indicator.nbits) - 1))
                    << (dci_size - pos);
        // DMRS sequence initialization
        pos += dci_pdu_rel15->dmrs_sequence_initialization.nbits;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->dmrs_sequence_initialization.val
                     & ((1 << dci_pdu_rel15->dmrs_sequence_initialization.nbits) - 1))
                    << (dci_size - pos);
        // UL-SCH indicator
        pos += 1;
        *dci_pdu |= ((uint64_t)dci_pdu_rel15->ulsch_indicator & 0x1) << (dci_size - pos);

#ifdef DEBUG_DCI
        LOG_I(NR_MAC,"============= NR_UL_DCI_FORMAT_0_1 =============\n");
        LOG_I(NR_MAC,"dci_size = %i\n", dci_size);
        LOG_I(NR_MAC,"dci_pdu_rel15->format_indicator = %i\n", dci_pdu_rel15->format_indicator);
        LOG_I(NR_MAC,"dci_pdu_rel15->carrier_indicator.val = %i\n", dci_pdu_rel15->carrier_indicator.val);
        LOG_I(NR_MAC,"dci_pdu_rel15->ul_sul_indicator.val = %i\n", dci_pdu_rel15->ul_sul_indicator.val);
        LOG_I(NR_MAC,"dci_pdu_rel15->bwp_indicator.val = %i\n", dci_pdu_rel15->bwp_indicator.val);
        LOG_I(NR_MAC,"dci_pdu_rel15->frequency_domain_assignment.val = %i\n", dci_pdu_rel15->frequency_domain_assignment.val);
        LOG_I(NR_MAC,"dci_pdu_rel15->time_domain_assignment.val = %i\n", dci_pdu_rel15->time_domain_assignment.val);
        LOG_I(NR_MAC,"dci_pdu_rel15->frequency_hopping_flag.val = %i\n", dci_pdu_rel15->frequency_hopping_flag.val);
        LOG_I(NR_MAC,"dci_pdu_rel15->mcs = %i\n", dci_pdu_rel15->mcs);
        LOG_I(NR_MAC,"dci_pdu_rel15->ndi = %i\n", dci_pdu_rel15->ndi);
        LOG_I(NR_MAC,"dci_pdu_rel15->rv= %i\n", dci_pdu_rel15->rv);
        LOG_I(NR_MAC,"dci_pdu_rel15->harq_pid = %i\n", dci_pdu_rel15->harq_pid);
        LOG_I(NR_MAC,"dci_pdu_rel15->dai[0].val = %i\n", dci_pdu_rel15->dai[0].val);
        LOG_I(NR_MAC,"dci_pdu_rel15->dai[1].val = %i\n", dci_pdu_rel15->dai[1].val);
        LOG_I(NR_MAC,"dci_pdu_rel15->tpc = %i\n", dci_pdu_rel15->tpc);
        LOG_I(NR_MAC,"dci_pdu_rel15->srs_resource_indicator.val = %i\n", dci_pdu_rel15->srs_resource_indicator.val);
        LOG_I(NR_MAC,"dci_pdu_rel15->precoding_information.val = %i\n", dci_pdu_rel15->precoding_information.val);
        LOG_I(NR_MAC,"dci_pdu_rel15->antenna_ports.val = %i\n", dci_pdu_rel15->antenna_ports.val);
        LOG_I(NR_MAC,"dci_pdu_rel15->srs_request.val = %i\n", dci_pdu_rel15->srs_request.val);
        LOG_I(NR_MAC,"dci_pdu_rel15->csi_request.val = %i\n", dci_pdu_rel15->csi_request.val);
        LOG_I(NR_MAC,"dci_pdu_rel15->cbgti.val = %i\n", dci_pdu_rel15->cbgti.val);
        LOG_I(NR_MAC,"dci_pdu_rel15->ptrs_dmrs_association.val = %i\n", dci_pdu_rel15->ptrs_dmrs_association.val);
        LOG_I(NR_MAC,"dci_pdu_rel15->beta_offset_indicator.val = %i\n", dci_pdu_rel15->beta_offset_indicator.val);
        LOG_I(NR_MAC,"dci_pdu_rel15->dmrs_sequence_initialization.val = %i\n", dci_pdu_rel15->dmrs_sequence_initialization.val);
        LOG_I(NR_MAC,"dci_pdu_rel15->ulsch_indicator = %i\n", dci_pdu_rel15->ulsch_indicator);
#endif

        break;
    }
    break;

  case NR_DL_DCI_FORMAT_1_1:
    // Indicating a DL DCI format 1bit
    LOG_D(NR_MAC,"Filling Format 1_1 DCI of size %d\n",dci_size);
    pos = 1;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->format_indicator & 0x1) << (dci_size - pos);
    // Carrier indicator
    pos += dci_pdu_rel15->carrier_indicator.nbits;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->carrier_indicator.val & ((1 << dci_pdu_rel15->carrier_indicator.nbits) - 1)) << (dci_size - pos);
    // BWP indicator
    pos += dci_pdu_rel15->bwp_indicator.nbits;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->bwp_indicator.val & ((1 << dci_pdu_rel15->bwp_indicator.nbits) - 1)) << (dci_size - pos);
    // Frequency domain resource assignment
    pos += dci_pdu_rel15->frequency_domain_assignment.nbits;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->frequency_domain_assignment.val & ((1 << dci_pdu_rel15->frequency_domain_assignment.nbits) - 1)) << (dci_size - pos);
    // Time domain resource assignment
    pos += dci_pdu_rel15->time_domain_assignment.nbits;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->time_domain_assignment.val & ((1 << dci_pdu_rel15->time_domain_assignment.nbits) - 1)) << (dci_size - pos);
    // VRB-to-PRB mapping
    pos += dci_pdu_rel15->vrb_to_prb_mapping.nbits;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->vrb_to_prb_mapping.val & ((1 << dci_pdu_rel15->vrb_to_prb_mapping.nbits) - 1)) << (dci_size - pos);
    // PRB bundling size indicator
    pos += dci_pdu_rel15->prb_bundling_size_indicator.nbits;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->prb_bundling_size_indicator.val & ((1 << dci_pdu_rel15->prb_bundling_size_indicator.nbits) - 1)) << (dci_size - pos);
    // Rate matching indicator
    pos += dci_pdu_rel15->rate_matching_indicator.nbits;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->rate_matching_indicator.val & ((1 << dci_pdu_rel15->rate_matching_indicator.nbits) - 1)) << (dci_size - pos);
    // ZP CSI-RS trigger
    pos += dci_pdu_rel15->zp_csi_rs_trigger.nbits;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->zp_csi_rs_trigger.val & ((1 << dci_pdu_rel15->zp_csi_rs_trigger.nbits) - 1)) << (dci_size - pos);
    // TB1
    // MCS 5bit
    pos += 5;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->mcs & 0x1f) << (dci_size - pos);
    // New data indicator 1bit
    pos += 1;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->ndi & 0x1) << (dci_size - pos);
    // Redundancy version  2bit
    pos += 2;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->rv & 0x3) << (dci_size - pos);
    // TB2
    // MCS 5bit
    pos += dci_pdu_rel15->mcs2.nbits;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->mcs2.val & ((1 << dci_pdu_rel15->mcs2.nbits) - 1)) << (dci_size - pos);
    // New data indicator 1bit
    pos += dci_pdu_rel15->ndi2.nbits;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->ndi2.val & ((1 << dci_pdu_rel15->ndi2.nbits) - 1)) << (dci_size - pos);
    // Redundancy version  2bit
    pos += dci_pdu_rel15->rv2.nbits;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->rv2.val & ((1 << dci_pdu_rel15->rv2.nbits) - 1)) << (dci_size - pos);
    // HARQ process number  4bit/5bit
    fsize = dci_pdu_rel15->harq_pid.nbits;
    pos += fsize;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->harq_pid.val & ((1 << fsize) - 1)) << (dci_size - pos);
    // Downlink assignment index
    pos += dci_pdu_rel15->dai[0].nbits;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->dai[0].val & ((1 << dci_pdu_rel15->dai[0].nbits) - 1)) << (dci_size - pos);
    // TPC command for scheduled PUCCH  2bit
    pos += 2;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->tpc & 0x3) << (dci_size - pos);
    // PUCCH resource indicator  3bit
    pos += 3;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->pucch_resource_indicator & 0x7) << (dci_size - pos);
    // PDSCH-to-HARQ_feedback timing indicator
    pos += dci_pdu_rel15->pdsch_to_harq_feedback_timing_indicator.nbits;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->pdsch_to_harq_feedback_timing_indicator.val & ((1 << dci_pdu_rel15->pdsch_to_harq_feedback_timing_indicator.nbits) - 1)) << (dci_size - pos);
    // Antenna ports
    pos += dci_pdu_rel15->antenna_ports.nbits;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->antenna_ports.val & ((1 << dci_pdu_rel15->antenna_ports.nbits) - 1)) << (dci_size - pos);
    // TCI
    pos += dci_pdu_rel15->transmission_configuration_indication.nbits;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->transmission_configuration_indication.val & ((1 << dci_pdu_rel15->transmission_configuration_indication.nbits) - 1)) << (dci_size - pos);
    // SRS request
    pos += dci_pdu_rel15->srs_request.nbits;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->srs_request.val & ((1 << dci_pdu_rel15->srs_request.nbits) - 1)) << (dci_size - pos);
    // CBG transmission information
    pos += dci_pdu_rel15->cbgti.nbits;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->cbgti.val & ((1 << dci_pdu_rel15->cbgti.nbits) - 1)) << (dci_size - pos);
    // CBG flushing out information
    pos += dci_pdu_rel15->cbgfi.nbits;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->cbgfi.val & ((1 << dci_pdu_rel15->cbgfi.nbits) - 1)) << (dci_size - pos);
    // DMRS sequence init
    pos += 1;
    *dci_pdu |= ((uint64_t)dci_pdu_rel15->dmrs_sequence_initialization.val & 0x1) << (dci_size - pos);
  }
  LOG_D(NR_MAC, "DCI has %d bits and the payload is %lx\n", dci_size, *dci_pdu);
}

int get_spf(nfapi_nr_config_request_scf_t *cfg) {

  int mu = cfg->ssb_config.scs_common.value;
  AssertFatal(mu>=0&&mu<4,"Illegal scs %d\n",mu);

  return(10 * (1<<mu));
} 

int to_absslot(nfapi_nr_config_request_scf_t *cfg,int frame,int slot) {

  return(get_spf(cfg)*frame) + slot; 

}

int extract_startSymbol(int startSymbolAndLength) {
  int tmp = startSymbolAndLength/14;
  int tmp2 = startSymbolAndLength%14;

  if (tmp > 0 && tmp < (14-tmp2)) return(tmp2);
  else                            return(13-tmp2);
}

int extract_length(int startSymbolAndLength) {
  int tmp = startSymbolAndLength/14;
  int tmp2 = startSymbolAndLength%14;

  if (tmp > 0 && tmp < (14-tmp2)) return(tmp);
  else                            return(15-tmp2);
}

/*
 * Dump the UL or DL UE_info into LOG_T(MAC)
 */
void dump_nr_list(NR_UE_info_t **list)
{
  UE_iterator(list, UE) {
    LOG_T(NR_MAC, "NR list UEs rntis %04x\n", (*list)->rnti);
  }
}

/*
 * Create a new NR_list
 */
void create_nr_list(NR_list_t *list, int len)
{
  list->head = -1;
  list->next = malloc(len * sizeof(*list->next));
  AssertFatal(list->next, "cannot malloc() memory for NR_list_t->next\n");
  for (int i = 0; i < len; ++i)
    list->next[i] = -1;
  list->tail = -1;
  list->len = len;
}

/*
 * Resize an NR_list
 */
void resize_nr_list(NR_list_t *list, int new_len)
{
  if (new_len == list->len)
    return;
  if (new_len > list->len) {
    /* list->head remains */
    const int old_len = list->len;
    int* n = realloc(list->next, new_len * sizeof(*list->next));
    AssertFatal(n, "cannot realloc() memory for NR_list_t->next\n");
    list->next = n;
    for (int i = old_len; i < new_len; ++i)
      list->next[i] = -1;
    /* list->tail remains */
    list->len = new_len;
  } else { /* new_len < len */
    AssertFatal(list->head < new_len, "shortened list head out of index %d (new len %d)\n", list->head, new_len);
    AssertFatal(list->tail < new_len, "shortened list tail out of index %d (new len %d)\n", list->head, new_len);
    for (int i = 0; i < list->len; ++i)
      AssertFatal(list->next[i] < new_len, "shortened list entry out of index %d (new len %d)\n", list->next[i], new_len);
    /* list->head remains */
    int *n = realloc(list->next, new_len * sizeof(*list->next));
    AssertFatal(n, "cannot realloc() memory for NR_list_t->next\n");
    list->next = n;
    /* list->tail remains */
    list->len = new_len;
  }
}

/*
 * Destroy an NR_list
 */
void destroy_nr_list(NR_list_t *list)
{
  free(list->next);
}

/*
 * Add an ID to an NR_list at the end, traversing the whole list. Note:
 * add_tail_nr_list() is a faster alternative, but this implementation ensures
 * we do not add an existing ID.
 */
void add_nr_list(NR_list_t *listP, int id)
{
  int *cur = &listP->head;
  while (*cur >= 0) {
    AssertFatal(*cur != id, "id %d already in NR_UE_list!\n", id);
    cur = &listP->next[*cur];
  }
  *cur = id;
  if (listP->next[id] < 0)
    listP->tail = id;
}

/*
 * Remove an ID from an NR_list
 */
void remove_nr_list(NR_list_t *listP, int id)
{
  int *cur = &listP->head;
  int *prev = &listP->head;
  while (*cur != -1 && *cur != id) {
    prev = cur;
    cur = &listP->next[*cur];
  }
  AssertFatal(*cur != -1, "ID %d not found in UE_list\n", id);
  int *next = &listP->next[*cur];
  *cur = listP->next[*cur];
  *next = -1;
  listP->tail = *prev >= 0 && listP->next[*prev] >= 0 ? listP->tail : *prev;
}

/*
 * Add an ID to the tail of the NR_list in O(1). Note that there is
 * corresponding remove_tail_nr_list(), as we cannot set the tail backwards and
 * therefore need to go through the whole list (use remove_nr_list())
 */
void add_tail_nr_list(NR_list_t *listP, int id)
{
  int *last = listP->tail < 0 ? &listP->head : &listP->next[listP->tail];
  *last = id;
  listP->next[id] = -1;
  listP->tail = id;
}

/*
 * Add an ID to the front of the NR_list in O(1)
 */
void add_front_nr_list(NR_list_t *listP, int id)
{
  const int ohead = listP->head;
  listP->head = id;
  listP->next[id] = ohead;
  if (listP->tail < 0)
    listP->tail = id;
}

/*
 * Remove an ID from the front of the NR_list in O(1)
 */
void remove_front_nr_list(NR_list_t *listP)
{
  AssertFatal(listP->head >= 0, "Nothing to remove\n");
  const int ohead = listP->head;
  listP->head = listP->next[ohead];
  listP->next[ohead] = -1;
  if (listP->head < 0)
    listP->tail = -1;
}

NR_UE_info_t *find_nr_UE(NR_UEs_t *UEs, rnti_t rntiP)
{
  UE_iterator(UEs->connected_ue_list, UE) {
    if (UE->rnti == rntiP) {
      LOG_D(NR_MAC,"Search and found rnti: %04x\n", rntiP);
      return UE;
    }
  }
  return NULL;
}

NR_UE_info_t *find_ra_UE(NR_UEs_t *UEs, rnti_t rntiP)
{
  UE_iterator(UEs->access_ue_list, UE) {
    if (UE->rnti == rntiP) {
      LOG_D(NR_MAC,"Search and found rnti: %04x\n", rntiP);
      return UE;
    }
  }
  return NULL;
}

void delete_nr_ue_data(NR_UE_info_t *UE, NR_COMMON_channels_t *ccPtr, uid_allocator_t *uia)
{
  ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->CellGroup);
  ASN_STRUCT_FREE(asn_DEF_NR_SpCellConfig, UE->reconfigSpCellConfig);
  ASN_STRUCT_FREE(asn_DEF_NR_UE_NR_Capability, UE->capability);
  NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
  seq_arr_free(&sched_ctrl->lc_config, NULL);
  destroy_nr_list(&sched_ctrl->available_dl_harq);
  destroy_nr_list(&sched_ctrl->feedback_dl_harq);
  destroy_nr_list(&sched_ctrl->retrans_dl_harq);
  destroy_nr_list(&sched_ctrl->available_ul_harq);
  destroy_nr_list(&sched_ctrl->feedback_ul_harq);
  destroy_nr_list(&sched_ctrl->retrans_ul_harq);
  for (int i = 0; i < NR_MAX_HARQ_PROCESSES; ++i)
    free_transportBlock_buffer(&sched_ctrl->harq_processes[i].transportBlock);
  free_sched_pucch_list(sched_ctrl);
  uid_linear_allocator_free(uia, UE->uid);
  LOG_I(NR_MAC, "Remove NR rnti 0x%04x\n", UE->rnti);
  free_and_zero(UE->ra);
  free(UE);
}

#define TB_SINGLE_LAYER (32 * 1024)
uint8_t *allocate_transportBlock_buffer(byte_array_t *tb, uint32_t needed)
{
  DevAssert(needed > 0);
  if (tb->buf != NULL && needed <= tb->len)
    return tb->buf; // nothing to do, current is enough

  uint32_t size = TB_SINGLE_LAYER;
  while (needed > size)
    size *= 2;
  LOG_D(NR_MAC, "allocating new TB block of size %d\n", size);
  free(tb->buf);
  tb->buf = malloc_or_fail(size);
  tb->len = size;
  return tb->buf;
}

void free_transportBlock_buffer(byte_array_t *tb)
{
  free_byte_array(*tb);
}

void set_max_fb_time(NR_UE_UL_BWP_t *UL_BWP, const NR_UE_DL_BWP_t *DL_BWP)
{
  UL_BWP->max_fb_time = 8; // default value
  // take the maximum in dl_DataToUL_ACK list
  if (UL_BWP->pucch_Config) {
    const struct NR_PUCCH_Config__dl_DataToUL_ACK *fb_times = UL_BWP->pucch_Config->dl_DataToUL_ACK;
    for (int i = 0; i < fb_times->list.count; i++) {
      if(*fb_times->list.array[i] > UL_BWP->max_fb_time)
        UL_BWP->max_fb_time = *fb_times->list.array[i];
    }
  }
}

void reset_sched_ctrl(NR_UE_sched_ctrl_t *sched_ctrl)
{
  sched_ctrl->srs_feedback.ul_ri = 0;
  sched_ctrl->srs_feedback.tpmi = 0;
  sched_ctrl->srs_feedback.sri = 0;
}

int get_dlbw_tbslbrm(int scc_bwpsize, const NR_ServingCellConfig_t *servingCellConfig)
{
  int bw = scc_bwpsize;
  if (servingCellConfig) {
    if (servingCellConfig->downlinkBWP_ToAddModList) {
      const struct NR_ServingCellConfig__downlinkBWP_ToAddModList *BWP_list = servingCellConfig->downlinkBWP_ToAddModList;
      for (int i = 0; i < BWP_list->list.count; i++) {
        NR_BWP_t genericParameters = BWP_list->list.array[i]->bwp_Common->genericParameters;
        int curr_bw = NRRIV2BW(genericParameters.locationAndBandwidth, MAX_BWP_SIZE);
        if (curr_bw > bw)
          bw = curr_bw;
      }
    }
  }
  return bw;
}

int get_ulbw_tbslbrm(int scc_bwpsize, const NR_ServingCellConfig_t *servingCellConfig)
{
  int bw = scc_bwpsize;
  if (servingCellConfig) {
    if (servingCellConfig->uplinkConfig && servingCellConfig->uplinkConfig->uplinkBWP_ToAddModList) {
      const struct NR_UplinkConfig__uplinkBWP_ToAddModList *BWP_list = servingCellConfig->uplinkConfig->uplinkBWP_ToAddModList;
      for (int i = 0; i < BWP_list->list.count; i++) {
        NR_BWP_t genericParameters = BWP_list->list.array[i]->bwp_Common->genericParameters;
        int curr_bw = NRRIV2BW(genericParameters.locationAndBandwidth, MAX_BWP_SIZE);
        if (curr_bw > bw)
          bw = curr_bw;
      }
    }
  }
  return bw;
}

static void set_sched_pucch_list(NR_UE_sched_ctrl_t *sched_ctrl,
                                 const NR_UE_UL_BWP_t *ul_bwp,
                                 const NR_ServingCellConfigCommon_t *scc,
                                 const frame_structure_t *fs)
{
  const int NTN_gNB_Koffset = get_NTN_Koffset(scc);
  const int n_ul_slots_period = get_ul_slots_per_period(fs);

  // PUCCH list size is given by the number of UL slots in the PUCCH period
  // the length PUCCH period is determined by max_fb_time since we may need to prepare PUCCH for ACK/NACK max_fb_time slots ahead
  const int list_size = n_ul_slots_period << (int)ceil(log2((ul_bwp->max_fb_time + NTN_gNB_Koffset) / fs->numb_slots_period + 1));

  if (!sched_ctrl->sched_pucch) {
    sched_ctrl->sched_pucch = calloc_or_fail(list_size, sizeof(*sched_ctrl->sched_pucch));
    sched_ctrl->sched_pucch_size = list_size;
  } else if (list_size > sched_ctrl->sched_pucch_size) {
    sched_ctrl->sched_pucch = realloc(sched_ctrl->sched_pucch, list_size * sizeof(*sched_ctrl->sched_pucch));
    for (int i = sched_ctrl->sched_pucch_size; i < list_size; i++) {
      NR_sched_pucch_t *curr_pucch = &sched_ctrl->sched_pucch[i];
      memset(curr_pucch, 0, sizeof(*curr_pucch));
    }
    sched_ctrl->sched_pucch_size = list_size;
  }
}

static void create_dl_harq_list(NR_UE_sched_ctrl_t *sched_ctrl, const NR_UE_ServingCell_Info_t *sc_info, bool dci_00_10)
{
  int nrofHARQ = get_nrofHARQ_ProcessesForPDSCH(sc_info);
  // limit the number of HARQ PIDs to 16 in case of common search space
  if (dci_00_10 && nrofHARQ > 16)
    nrofHARQ = 16;
  // add all available DL HARQ processes for this UE
  AssertFatal(sched_ctrl->available_dl_harq.len == sched_ctrl->feedback_dl_harq.len
              && sched_ctrl->available_dl_harq.len == sched_ctrl->retrans_dl_harq.len,
              "HARQ lists have different lengths (%d/%d/%d)\n",
              sched_ctrl->available_dl_harq.len,
              sched_ctrl->feedback_dl_harq.len,
              sched_ctrl->retrans_dl_harq.len);
  if (sched_ctrl->available_dl_harq.len == 0) {
    create_nr_list(&sched_ctrl->available_dl_harq, nrofHARQ);
    for (int harq = 0; harq < nrofHARQ; harq++)
      add_tail_nr_list(&sched_ctrl->available_dl_harq, harq);
    create_nr_list(&sched_ctrl->feedback_dl_harq, nrofHARQ);
    create_nr_list(&sched_ctrl->retrans_dl_harq, nrofHARQ);
  } else if (sched_ctrl->available_dl_harq.len == nrofHARQ) {
    LOG_D(NR_MAC, "nrofHARQ %d already configured\n", nrofHARQ);
  } else {
    const int old_nrofHARQ = sched_ctrl->available_dl_harq.len;
    AssertFatal(nrofHARQ > old_nrofHARQ,
                "cannot resize HARQ list to be smaller (nrofHARQ %d, old_nrofHARQ %d)\n",
                nrofHARQ, old_nrofHARQ);
    resize_nr_list(&sched_ctrl->available_dl_harq, nrofHARQ);
    for (int harq = old_nrofHARQ; harq < nrofHARQ; harq++)
      add_tail_nr_list(&sched_ctrl->available_dl_harq, harq);
    resize_nr_list(&sched_ctrl->feedback_dl_harq, nrofHARQ);
    resize_nr_list(&sched_ctrl->retrans_dl_harq, nrofHARQ);
  }
}

static void create_ul_harq_list(NR_UE_sched_ctrl_t *sched_ctrl, const NR_UE_ServingCell_Info_t *sc_info, bool dci_00_10)
{
  int nrofHARQ = get_nrofHARQ_ProcessesForPUSCH(sc_info);
  // limit the number of HARQ PIDs to 16 in case of common search space
  if (dci_00_10 && nrofHARQ > 16)
    nrofHARQ = 16;
  if (sched_ctrl->available_ul_harq.len == 0) {
    create_nr_list(&sched_ctrl->available_ul_harq, nrofHARQ);
    for (int harq = 0; harq < nrofHARQ; harq++)
      add_tail_nr_list(&sched_ctrl->available_ul_harq, harq);
    create_nr_list(&sched_ctrl->feedback_ul_harq, nrofHARQ);
    create_nr_list(&sched_ctrl->retrans_ul_harq, nrofHARQ);
  } else if (sched_ctrl->available_ul_harq.len == nrofHARQ) {
    LOG_D(NR_MAC, "nrofHARQ %d already configured\n", nrofHARQ);
  } else {
    const int old_nrofHARQ = sched_ctrl->available_ul_harq.len;
    AssertFatal(nrofHARQ > old_nrofHARQ,
                "cannot resize HARQ list to be smaller (nrofHARQ %d, old_nrofHARQ %d)\n",
                nrofHARQ, old_nrofHARQ);
    resize_nr_list(&sched_ctrl->available_ul_harq, nrofHARQ);
    for (int harq = old_nrofHARQ; harq < nrofHARQ; harq++)
      add_tail_nr_list(&sched_ctrl->available_ul_harq, harq);
    resize_nr_list(&sched_ctrl->feedback_ul_harq, nrofHARQ);
    resize_nr_list(&sched_ctrl->retrans_ul_harq, nrofHARQ);
  }
}

// main function to configure parameters of current BWP
void configure_UE_BWP(gNB_MAC_INST *nr_mac,
                      NR_ServingCellConfigCommon_t *scc,
                      NR_UE_info_t *UE,
                      bool is_RA,
                      int target_ss,
                      int dl_bwp_switch,
                      int ul_bwp_switch)
{
  NR_CellGroupConfig_t *CellGroup = UE->CellGroup;
  NR_UE_ServingCell_Info_t *sc_info = &UE->sc_info;
  NR_UE_DL_BWP_t *DL_BWP = &UE->current_DL_BWP;
  NR_UE_UL_BWP_t *UL_BWP = &UE->current_UL_BWP;

  NR_BWP_Downlink_t *dl_bwp = NULL;
  NR_BWP_Uplink_t *ul_bwp = NULL;
  NR_BWP_DownlinkDedicated_t *bwpd = NULL;
  NR_BWP_UplinkDedicated_t *ubwpd = NULL;
  // number of additional BWPs (excluding initial BWP)
  sc_info->n_dl_bwp = 0;
  sc_info->n_ul_bwp = 0;
  int old_dl_bwp_id = DL_BWP->bwp_id;
  int old_ul_bwp_id = UL_BWP->bwp_id;

  NR_ServingCellConfig_t *servingCellConfig = NULL;
  if (CellGroup && CellGroup->spCellConfig && CellGroup->spCellConfig->spCellConfigDedicated) {

    servingCellConfig  = CellGroup->spCellConfig->spCellConfigDedicated;

    if(dl_bwp_switch >= 0 && ul_bwp_switch >= 0) {
      AssertFatal(dl_bwp_switch == ul_bwp_switch, "Different UL and DL BWP not supported\n");
      DL_BWP->bwp_id = dl_bwp_switch;
      UL_BWP->bwp_id = ul_bwp_switch;
    }
    else {
      // (re)configuring BWP
      // TODO BWP switching not via RRC reconfiguration
      // via RRC if firstActiveXlinkBWP_Id is NULL, MAC stays on the same BWP as before
      if (servingCellConfig->firstActiveDownlinkBWP_Id)
        DL_BWP->bwp_id = *servingCellConfig->firstActiveDownlinkBWP_Id;
      if (servingCellConfig->uplinkConfig->firstActiveUplinkBWP_Id)
        UL_BWP->bwp_id = *servingCellConfig->uplinkConfig->firstActiveUplinkBWP_Id;
    }

    const struct NR_ServingCellConfig__downlinkBWP_ToAddModList *bwpList = servingCellConfig->downlinkBWP_ToAddModList;
    if(bwpList)
      sc_info->n_dl_bwp = bwpList->list.count;
    if (DL_BWP->bwp_id>0) {
      for (int i=0; i<bwpList->list.count; i++) {
        dl_bwp = bwpList->list.array[i];
        if(dl_bwp->bwp_Id == DL_BWP->bwp_id)
          break;
      }
      AssertFatal(dl_bwp!=NULL,"Couldn't find DLBWP corresponding to BWP ID %ld\n",DL_BWP->bwp_id);
    }

    const struct NR_UplinkConfig__uplinkBWP_ToAddModList *ubwpList = servingCellConfig->uplinkConfig->uplinkBWP_ToAddModList;
    if(ubwpList)
      sc_info->n_ul_bwp = ubwpList->list.count;
    if (UL_BWP->bwp_id>0) {
      for (int i=0; i<ubwpList->list.count; i++) {
        ul_bwp = ubwpList->list.array[i];
        if(ul_bwp->bwp_Id == UL_BWP->bwp_id)
          break;
      }
      AssertFatal(ul_bwp!=NULL,"Couldn't find ULBWP corresponding to BWP ID %ld\n",UL_BWP->bwp_id);
    }

    // selection of dedicated BWPs
    if(dl_bwp)
      bwpd = dl_bwp->bwp_Dedicated;
    else
      bwpd = servingCellConfig->initialDownlinkBWP;
    if(ul_bwp)
      ubwpd = ul_bwp->bwp_Dedicated;
    else
      ubwpd = servingCellConfig->uplinkConfig->initialUplinkBWP;

    DL_BWP->pdsch_Config = bwpd->pdsch_Config->choice.setup;
    UL_BWP->configuredGrantConfig = ubwpd->configuredGrantConfig ? ubwpd->configuredGrantConfig->choice.setup : NULL;
    UL_BWP->pusch_Config = ubwpd->pusch_Config->choice.setup;
    UL_BWP->pucch_Config = ubwpd->pucch_Config->choice.setup;
    UL_BWP->srs_Config = ubwpd->srs_Config->choice.setup;
  }
  else {
    DL_BWP->bwp_id = 0;
    UL_BWP->bwp_id = 0;
    DL_BWP->pdsch_Config = NULL;
    UL_BWP->pusch_Config = NULL;
    UL_BWP->pucch_Config = NULL;
    UL_BWP->configuredGrantConfig = NULL;
  }

  if (old_dl_bwp_id != DL_BWP->bwp_id)
    LOG_I(NR_MAC, "Switching to DL-BWP %li\n", DL_BWP->bwp_id);
  if (old_ul_bwp_id != UL_BWP->bwp_id)
    LOG_I(NR_MAC, "Switching to UL-BWP %li\n", UL_BWP->bwp_id);

  // TDA lists
  if (DL_BWP->bwp_id>0)
    DL_BWP->tdaList_Common = dl_bwp->bwp_Common->pdsch_ConfigCommon->choice.setup->pdsch_TimeDomainAllocationList;
  else
    DL_BWP->tdaList_Common = scc->downlinkConfigCommon->initialDownlinkBWP->pdsch_ConfigCommon->choice.setup->pdsch_TimeDomainAllocationList;

  if(UL_BWP->bwp_id>0)
    UL_BWP->tdaList_Common = ul_bwp->bwp_Common->pusch_ConfigCommon->choice.setup->pusch_TimeDomainAllocationList;
  else
    UL_BWP->tdaList_Common = scc->uplinkConfigCommon->initialUplinkBWP->pusch_ConfigCommon->choice.setup->pusch_TimeDomainAllocationList;

  // setting generic parameters
  NR_BWP_t dl_genericParameters = (DL_BWP->bwp_id > 0 && dl_bwp) ?
    dl_bwp->bwp_Common->genericParameters:
    scc->downlinkConfigCommon->initialDownlinkBWP->genericParameters;

  DL_BWP->scs = dl_genericParameters.subcarrierSpacing;
  DL_BWP->cyclicprefix = dl_genericParameters.cyclicPrefix;
  DL_BWP->BWPSize = NRRIV2BW(dl_genericParameters.locationAndBandwidth, MAX_BWP_SIZE);
  DL_BWP->BWPStart = NRRIV2PRBOFFSET(dl_genericParameters.locationAndBandwidth, MAX_BWP_SIZE);
  sc_info->initial_dl_BWPSize =
      NRRIV2BW(scc->downlinkConfigCommon->initialDownlinkBWP->genericParameters.locationAndBandwidth, MAX_BWP_SIZE);
  sc_info->initial_dl_BWPStart =
      NRRIV2PRBOFFSET(scc->downlinkConfigCommon->initialDownlinkBWP->genericParameters.locationAndBandwidth, MAX_BWP_SIZE);

  NR_BWP_t ul_genericParameters = (UL_BWP->bwp_id > 0 && ul_bwp) ?
    ul_bwp->bwp_Common->genericParameters:
    scc->uplinkConfigCommon->initialUplinkBWP->genericParameters;

  UL_BWP->scs = ul_genericParameters.subcarrierSpacing;
  UL_BWP->cyclicprefix = ul_genericParameters.cyclicPrefix;
  UL_BWP->BWPSize = NRRIV2BW(ul_genericParameters.locationAndBandwidth, MAX_BWP_SIZE);
  UL_BWP->BWPStart = NRRIV2PRBOFFSET(ul_genericParameters.locationAndBandwidth, MAX_BWP_SIZE);
  sc_info->initial_ul_BWPSize =
      NRRIV2BW(scc->uplinkConfigCommon->initialUplinkBWP->genericParameters.locationAndBandwidth, MAX_BWP_SIZE);
  sc_info->initial_ul_BWPStart =
      NRRIV2PRBOFFSET(scc->uplinkConfigCommon->initialUplinkBWP->genericParameters.locationAndBandwidth, MAX_BWP_SIZE);

  sc_info->dl_bw_tbslbrm = get_dlbw_tbslbrm(sc_info->initial_dl_BWPSize, servingCellConfig);
  sc_info->ul_bw_tbslbrm = get_ulbw_tbslbrm(sc_info->initial_ul_BWPSize, servingCellConfig);

  if (UL_BWP->bwp_id > 0) {
    UL_BWP->pucch_ConfigCommon = ul_bwp->bwp_Common->pucch_ConfigCommon ? ul_bwp->bwp_Common->pucch_ConfigCommon->choice.setup : NULL;
    UL_BWP->rach_ConfigCommon = ul_bwp->bwp_Common->rach_ConfigCommon ? ul_bwp->bwp_Common->rach_ConfigCommon->choice.setup : NULL;
  } else {
    UL_BWP->pucch_ConfigCommon = scc->uplinkConfigCommon->initialUplinkBWP->pucch_ConfigCommon->choice.setup;
    UL_BWP->rach_ConfigCommon = scc->uplinkConfigCommon->initialUplinkBWP->rach_ConfigCommon->choice.setup;
  }

  NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
  // Reset required fields in sched_ctrl (e.g. ul_ri and tpmi)
  reset_sched_ctrl(sched_ctrl);
  if(!is_RA) {
    if (servingCellConfig) {
      if (servingCellConfig->csi_MeasConfig) {
        sc_info->csi_MeasConfig = servingCellConfig->csi_MeasConfig->choice.setup;
        compute_csi_bitlen(sc_info->csi_MeasConfig, UE->csi_report_template);
      }
      if (servingCellConfig->pdsch_ServingCellConfig && servingCellConfig->pdsch_ServingCellConfig->choice.setup) {
        NR_PDSCH_ServingCellConfig_t *pdsch_servingcellconfig = servingCellConfig->pdsch_ServingCellConfig->choice.setup;
        sc_info->nrofHARQ_ProcessesForPDSCH = pdsch_servingcellconfig->nrofHARQ_ProcessesForPDSCH;
        if (pdsch_servingcellconfig->ext1)
          sc_info->maxMIMO_Layers_PDSCH = pdsch_servingcellconfig->ext1->maxMIMO_Layers;
        if (pdsch_servingcellconfig->ext3 &&
            pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17)
          sc_info->downlinkHARQ_FeedbackDisabled_r17 = &pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup;
        if (pdsch_servingcellconfig->codeBlockGroupTransmission
            && pdsch_servingcellconfig->codeBlockGroupTransmission->choice.setup)
          sc_info->pdsch_CGB_Transmission = pdsch_servingcellconfig->codeBlockGroupTransmission->choice.setup;
        if (pdsch_servingcellconfig->ext3)
          sc_info->nrofHARQ_ProcessesForPDSCH_v1700 = pdsch_servingcellconfig->ext3->nrofHARQ_ProcessesForPDSCH_v1700;
      }
      sc_info->crossCarrierSchedulingConfig = servingCellConfig->crossCarrierSchedulingConfig;
      sc_info->supplementaryUplink = servingCellConfig->supplementaryUplink;
      if (servingCellConfig->uplinkConfig) {
        if (servingCellConfig->uplinkConfig->carrierSwitching)
          sc_info->carrierSwitching = servingCellConfig->uplinkConfig->carrierSwitching->choice.setup;
        if (servingCellConfig->uplinkConfig->pusch_ServingCellConfig
            && servingCellConfig->uplinkConfig->pusch_ServingCellConfig->choice.setup) {
          NR_PUSCH_ServingCellConfig_t *pusch_servingcellconfig =
              servingCellConfig->uplinkConfig->pusch_ServingCellConfig->choice.setup;
          if (pusch_servingcellconfig->ext1)
            sc_info->maxMIMO_Layers_PUSCH = pusch_servingcellconfig->ext1->maxMIMO_Layers;
          sc_info->rateMatching_PUSCH = pusch_servingcellconfig->rateMatching;
          if (pusch_servingcellconfig->codeBlockGroupTransmission
              && pusch_servingcellconfig->codeBlockGroupTransmission->choice.setup)
            sc_info->pusch_CGB_Transmission = pusch_servingcellconfig->codeBlockGroupTransmission->choice.setup;
          if (pusch_servingcellconfig->ext3)
            sc_info->nrofHARQ_ProcessesForPUSCH_r17 = pusch_servingcellconfig->ext3->nrofHARQ_ProcessesForPUSCH_r17;
        }
      }
    }

    if (CellGroup && CellGroup->physicalCellGroupConfig)
      UE->pdsch_HARQ_ACK_Codebook = CellGroup->physicalCellGroupConfig->pdsch_HARQ_ACK_Codebook;

    // setting PDCCH related structures for sched_ctrl
    sched_ctrl->search_space = get_searchspace(scc, bwpd, target_ss);
    sched_ctrl->coreset = get_coreset(nr_mac, scc, bwpd, *sched_ctrl->search_space->controlResourceSetId);

    sched_ctrl->sched_pdcch = set_pdcch_structure(nr_mac,
                                                  sched_ctrl->search_space,
                                                  sched_ctrl->coreset,
                                                  scc,
                                                  &dl_genericParameters,
                                                  nr_mac->type0_PDCCH_CSS_config);

    // set DL DCI format
    DL_BWP->dci_format = (sched_ctrl->search_space->searchSpaceType &&
                         sched_ctrl->search_space->searchSpaceType->present == NR_SearchSpace__searchSpaceType_PR_ue_Specific) ?
                         (sched_ctrl->search_space->searchSpaceType->choice.ue_Specific->dci_Formats == NR_SearchSpace__searchSpaceType__ue_Specific__dci_Formats_formats0_1_And_1_1 ?
                         NR_DL_DCI_FORMAT_1_1 : NR_DL_DCI_FORMAT_1_0) :
                         NR_DL_DCI_FORMAT_1_0;
    // set UL DCI format
    UL_BWP->dci_format = (sched_ctrl->search_space->searchSpaceType &&
                         sched_ctrl->search_space->searchSpaceType->present == NR_SearchSpace__searchSpaceType_PR_ue_Specific) ?
                         (sched_ctrl->search_space->searchSpaceType->choice.ue_Specific->dci_Formats == NR_SearchSpace__searchSpaceType__ue_Specific__dci_Formats_formats0_1_And_1_1 ?
                         NR_UL_DCI_FORMAT_0_1 : NR_UL_DCI_FORMAT_0_0) :
                         NR_UL_DCI_FORMAT_0_0;

  } else {
    // setting PDCCH related structures for RA
    struct NR_PDCCH_ConfigCommon__commonSearchSpaceList *commonSearchSpaceList = NULL;
    NR_SearchSpaceId_t ra_SearchSpace = 0;
    if(dl_bwp) {
      commonSearchSpaceList = dl_bwp->bwp_Common->pdcch_ConfigCommon->choice.setup->commonSearchSpaceList;
      ra_SearchSpace = *dl_bwp->bwp_Common->pdcch_ConfigCommon->choice.setup->ra_SearchSpace;
    } else {
      commonSearchSpaceList = scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->commonSearchSpaceList;
      ra_SearchSpace = *scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->ra_SearchSpace;
    }
    AssertFatal(commonSearchSpaceList->list.count > 0, "common SearchSpace list has 0 elements\n");
    for (int i = 0; i < commonSearchSpaceList->list.count; i++) {
      NR_SearchSpace_t *ss = commonSearchSpaceList->list.array[i];
      if (ss->searchSpaceId == ra_SearchSpace)
        sched_ctrl->search_space = ss;
    }
    AssertFatal(sched_ctrl->search_space != NULL, "SearchSpace cannot be null for RA\n");

    sched_ctrl->coreset = get_coreset(nr_mac, scc, bwpd, *sched_ctrl->search_space->controlResourceSetId);
    NR_COMMON_channels_t *cc = &nr_mac->common_channels[0];
    int ssb_index = cc->ssb_index[UE->UE_beam_index];
    sched_ctrl->sched_pdcch = set_pdcch_structure(nr_mac,
                                                  sched_ctrl->search_space,
                                                  sched_ctrl->coreset,
                                                  scc,
                                                  &dl_genericParameters,
                                                  &nr_mac->type0_PDCCH_CSS_config[ssb_index]);

    UL_BWP->dci_format = NR_UL_DCI_FORMAT_0_0;
    DL_BWP->dci_format = NR_DL_DCI_FORMAT_1_0;
  }

  bool format_00_10 = DL_BWP->dci_format == NR_DL_DCI_FORMAT_1_0; // if DL is 10 UL is surely 00
  create_dl_harq_list(sched_ctrl, sc_info, format_00_10);
  create_ul_harq_list(sched_ctrl, sc_info, format_00_10);

  set_max_fb_time(UL_BWP, DL_BWP);
  set_sched_pucch_list(sched_ctrl, UL_BWP, scc, &nr_mac->frame_structure);

  // Set MCS tables
  long *dl_mcs_Table = DL_BWP->pdsch_Config ? DL_BWP->pdsch_Config->mcs_Table : NULL;
  DL_BWP->mcsTableIdx = get_pdsch_mcs_table(dl_mcs_Table, DL_BWP->dci_format, TYPE_C_RNTI_, target_ss);

  // 0 precoding enabled 1 precoding disabled
  UL_BWP->transform_precoding = get_transformPrecoding(UL_BWP, UL_BWP->dci_format, 0);
  // Set uplink MCS table
  long *mcs_Table = NULL;
  if (UL_BWP->pusch_Config)
    mcs_Table = UL_BWP->transform_precoding ? UL_BWP->pusch_Config->mcs_Table : UL_BWP->pusch_Config->mcs_TableTransformPrecoder;

  UL_BWP->mcs_table = get_pusch_mcs_table(mcs_Table, !UL_BWP->transform_precoding, UL_BWP->dci_format, TYPE_C_RNTI_, target_ss, false);
}

void reset_srs_stats(NR_UE_info_t *UE) {
  if (UE) {
    UE->mac_stats.srs_stats[0] = '\0';
  }
}

static void init_bler_stats(const NR_bler_options_t *bler_options, NR_bler_stats_t *bler_stats, frame_t frame)
{
  bler_stats->last_frame = frame;
  bler_stats->mcs = bler_options->min_mcs;
  bler_stats->bler = (float)(bler_options->lower + bler_options->upper) / 2.0f;
}

/* @brief returns a new UE allocated instance.
 *
 * It will be typically added to the access_ue_list, but not always (e.g.,
 * phytest mode), so this is not done in this function (and also, to allow
 * error handling). Remove with delete_nr_ue_data().  */
NR_UE_info_t *get_new_nr_ue_inst(uid_allocator_t *uia, rnti_t rnti, NR_CellGroupConfig_t *CellGroup)
{
  NR_UE_info_t *UE = calloc_or_fail(1, sizeof(NR_UE_info_t));
  UE->uid = uid_linear_allocator_new(uia);
  UE->rnti = rnti;
  UE->CellGroup = CellGroup;
  UE->ra = calloc(1, sizeof(*UE->ra));
  NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
  sched_ctrl->ta_update = 31;

  /* set illegal time domain allocation to force recomputation of all fields */
  sched_ctrl->sched_pdsch.time_domain_allocation = -1;

  /* Set default BWPs */
  AssertFatal(UE->sc_info.n_ul_bwp <= NR_MAX_NUM_BWP, "uplinkBWP_ToAddModList has %d BWP!\n", UE->sc_info.n_ul_bwp);

  // initialize LCID structure
  seq_arr_init(&sched_ctrl->lc_config, sizeof(nr_lc_config_t));
  return UE;
}

bool add_UE_to_list(int list_size, NR_UE_info_t *list[list_size], NR_UE_info_t *UE)
{
  for (int i = 0; i < list_size; i++) {
    if (!list[i]) {
      list[i] = UE;
      return true;
    }
  }
  return false;
}

NR_UE_info_t *remove_UE_from_list(int list_size, NR_UE_info_t *list[list_size], rnti_t rnti)
{
  for (int i = 0; i < list_size; i++) {
    NR_UE_info_t *curr_UE = list[i];
    if (!curr_UE)
      break; /* reached the end of the list */
    if (curr_UE->rnti != rnti)
      continue;

    /* remove this UE from the list, return the pointer */
    memmove(&list[i], &list[i+1], sizeof(list[0]) * (list_size - i - 1));
    list[list_size - 1] = NULL;
    return curr_UE;
  }
  return NULL;
}

/** @brief Transitions a UE from access list to connected list (i.e., the RA
 * list to the "normal" UE context list. */
bool transition_ra_connected_nr_ue(gNB_MAC_INST *nr_mac, NR_UE_info_t *UE)
{
  NR_UEs_t *UE_info = &nr_mac->UE_info;

  // remove UE from initial access list (moved to connected mode)
  NR_UE_info_t *r = remove_UE_from_list(NR_NB_RA_PROC_MAX, UE_info->access_ue_list, UE->rnti);
  DevAssert(r == UE); /* sanity check: we should have removed the current UE ptr from list */

  free_and_zero(UE->ra);

  return add_connected_nr_ue(nr_mac, UE);
}

/** @brief Add a UE to the list of UEs in * connected mode.
 *
 * To remove the UE, use mac_remove_nr_ue(). */
bool add_connected_nr_ue(gNB_MAC_INST *nr_mac, NR_UE_info_t *UE)
{
  LOG_I(NR_MAC, "Adding new UE context with RNTI 0x%04x\n", UE->rnti);
  NR_UEs_t *UE_info = &nr_mac->UE_info;
  dump_nr_list(UE_info->connected_ue_list);
  AssertFatal(!UE->ra, "UE in connected cannot have RA process\n");

  NR_SCHED_LOCK(&UE_info->mutex);

  bool success = add_UE_to_list(MAX_MOBILES_PER_GNB, UE_info->connected_ue_list, UE);
  if (!success) {
    LOG_E(NR_MAC,"Try to add UE %04x but the list is full\n", UE->rnti);
    delete_nr_ue_data(UE, NULL, &UE_info->uid_allocator);
    NR_SCHED_UNLOCK(&UE_info->mutex);
    return false;
  }

  NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
  sched_ctrl->dl_max_mcs = 28; /* do not limit MCS for individual UEs */
  sched_ctrl->pdcch_cl_adjust = 0;
  reset_srs_stats(UE);

  // Initialize bler_stats
  init_bler_stats(&nr_mac->dl_bler, &sched_ctrl->dl_bler_stats, nr_mac->frame);
  init_bler_stats(&nr_mac->ul_bler, &sched_ctrl->ul_bler_stats, nr_mac->frame);

  NR_SCHED_UNLOCK(&UE_info->mutex);
  dump_nr_list(UE_info->connected_ue_list);
  return true;
}

void free_sched_pucch_list(NR_UE_sched_ctrl_t *sched_ctrl)
{
  free(sched_ctrl->sched_pucch);
}

void reset_dl_harq_list(NR_UE_sched_ctrl_t *sched_ctrl) {
  int harq;
  while ((harq = sched_ctrl->feedback_dl_harq.head) >= 0) {
    remove_front_nr_list(&sched_ctrl->feedback_dl_harq);
    add_tail_nr_list(&sched_ctrl->available_dl_harq, harq);
  }

  while ((harq = sched_ctrl->retrans_dl_harq.head) >= 0) {
    remove_front_nr_list(&sched_ctrl->retrans_dl_harq);
    add_tail_nr_list(&sched_ctrl->available_dl_harq, harq);
  }

  for (int i = 0; i < NR_MAX_HARQ_PROCESSES; i++) {
    sched_ctrl->harq_processes[i].feedback_slot = -1;
    sched_ctrl->harq_processes[i].round = 0;
    sched_ctrl->harq_processes[i].is_waiting = false;
  }
}

void reset_ul_harq_list(NR_UE_sched_ctrl_t *sched_ctrl) {
  int harq;
  while ((harq = sched_ctrl->feedback_ul_harq.head) >= 0) {
    remove_front_nr_list(&sched_ctrl->feedback_ul_harq);
    add_tail_nr_list(&sched_ctrl->available_ul_harq, harq);
  }

  while ((harq = sched_ctrl->retrans_ul_harq.head) >= 0) {
    remove_front_nr_list(&sched_ctrl->retrans_ul_harq);
    add_tail_nr_list(&sched_ctrl->available_ul_harq, harq);
  }

  for (int i = 0; i < NR_MAX_HARQ_PROCESSES; i++) {
    sched_ctrl->ul_harq_processes[i].feedback_slot = -1;
    sched_ctrl->ul_harq_processes[i].round = 0;
    sched_ctrl->ul_harq_processes[i].is_waiting = false;
  }
}

void mac_remove_nr_ue(gNB_MAC_INST *nr_mac, rnti_t rnti)
{
  /* already mutex protected */
  NR_SCHED_ENSURE_LOCKED(&nr_mac->sched_lock);
  NR_UEs_t *UE_info = &nr_mac->UE_info;
  NR_SCHED_LOCK(&UE_info->mutex);
  NR_UE_info_t *UE = remove_UE_from_list(MAX_MOBILES_PER_GNB + 1, UE_info->connected_ue_list, rnti);
  NR_SCHED_UNLOCK(&UE_info->mutex);
  if (UE)
    delete_nr_ue_data(UE, nr_mac->common_channels, &UE_info->uid_allocator);
  else
    nr_release_ra_UE(nr_mac, rnti);
}

// all values passed to this function are in dB x10
uint8_t nr_get_tpc(int target, uint8_t cqi, int incr, int tx_power)
{
  // al values passed to this function are x10
  int snrx10 = (cqi * 5) - 640 - (tx_power * 10);
  LOG_D(NR_MAC, "tpc : target %d, cqi %d, snrx10 %d, tx_power %d\n", target, ((int)cqi * 5) - 640, snrx10, tx_power);
  if (snrx10 > target + incr) return 0; // decrease 1dB
  if (snrx10 < target - (3*incr)) return 3; // increase 3dB
  if (snrx10 < target - incr) return 2; // increase 1dB
  LOG_D(NR_MAC,"tpc : target %d, snrx10 %d\n",target,snrx10);
  return 1; // no change
}

/**
 * @brief Limits the power control commands (TPC) in NR by checking the RSSI threshold.
 *
 * This function evaluates the received signal strength indicator (RSSI) and adjusts the
 * transmit power control (TPC) commands accordingly to ensure they remain within acceptable
 * limits. It helps in maintaining the signal quality and preventing excessive power usage.
 *
 * @param rssi The received signal strength indicator value, as defined in FAPI specifications.
 * @param tpc The transmit power control command to be limited.
 * @param rssi_threshold RSSI threshold in 0.1 dBm/dBFS, range -1280 to 0
 * @return The adjusted TPC command after applying the RSSI threshold check.
 */
uint8_t nr_limit_tpc(int tpc, int rssi, int rssi_threshold)
{
  if (rssi == 0xFFFF) {
    // RSSI not available, keep tpc
    return tpc;
  }
  // Convert RSSI threshold to FAPI scale
  const int fapi_rssi_0dBm_or_0dBFS = 1280;
  int rssi_fapi_threshold = fapi_rssi_0dBm_or_0dBFS + rssi_threshold;
  // Further limit TPC if above or near RSSI threshold
  int tpc_to_db[] = {-1, 0, 1, 3};
  if (rssi > rssi_fapi_threshold) {
    // RSSI above theshold, reduce power
    return 0;
  } else if (rssi + tpc_to_db[tpc] * 10 > rssi_fapi_threshold) {
    // Cannot apply required TPC, check 1 dB increment
    if (rssi + 10 > rssi_fapi_threshold) {
      // Still cannot apply required TPC, keep power
      return 1;
    } else {
      // Can apply 1dB increment, but 3 was requested
      return 2;
    }
  }
  return tpc;
}

int get_pdsch_to_harq_feedback(NR_PUCCH_Config_t *pucch_Config,
                                nr_dci_format_t dci_format,
                                uint8_t *pdsch_to_harq_feedback) {
  /* already mutex protected: held in nr_acknack_scheduling() */

  if (dci_format == NR_DL_DCI_FORMAT_1_0) {
    for (int i = 0; i < 8; i++)
      pdsch_to_harq_feedback[i] = i + 1;
    return 8;
  }
  else {
    AssertFatal(pucch_Config != NULL && pucch_Config->dl_DataToUL_ACK != NULL,"dl_DataToUL_ACK shouldn't be null here\n");
    for (int i = 0; i < pucch_Config->dl_DataToUL_ACK->list.count; i++) {
      pdsch_to_harq_feedback[i] = *pucch_Config->dl_DataToUL_ACK->list.array[i];
    }
    return pucch_Config->dl_DataToUL_ACK->list.count;
  }
}

void nr_csirs_scheduling(int Mod_idP, frame_t frame, slot_t slot, nfapi_nr_dl_tti_request_t *DL_req)
{
  int CC_id = 0;
  NR_UEs_t *UE_info = &RC.nrmac[Mod_idP]->UE_info;
  gNB_MAC_INST *gNB_mac = RC.nrmac[Mod_idP];
  int n_slots_frame = gNB_mac->frame_structure.numb_slots_frame;
  NR_SCHED_ENSURE_LOCKED(&gNB_mac->sched_lock);

  UE_info->sched_csirs = 0;

  UE_iterator(UE_info->connected_ue_list, UE) {
    NR_UE_DL_BWP_t *dl_bwp = &UE->current_DL_BWP;

    // CSI-RS is common to all UEs in a given BWP
    // therefore we need to schedule only once per BWP
    // the following condition verifies if CSI-RS
    // has been already scheduled in this BWP
    if (UE_info->sched_csirs & (1 << dl_bwp->bwp_id))
      continue;
    NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
    if (nr_timer_is_active(&sched_ctrl->transm_interrupt))
      continue;

    if (!UE->sc_info.csi_MeasConfig)
      continue;

    NR_CSI_MeasConfig_t *csi_measconfig = UE->sc_info.csi_MeasConfig;

    // looking for the correct CSI-RS resource in current BWP
    NR_NZP_CSI_RS_ResourceSetId_t *nzp = NULL;
    for (int csi_list=0; csi_list<csi_measconfig->csi_ResourceConfigToAddModList->list.count; csi_list++) {
      NR_CSI_ResourceConfig_t *csires = csi_measconfig->csi_ResourceConfigToAddModList->list.array[csi_list];
      if(csires->bwp_Id == dl_bwp->bwp_id &&
         csires->csi_RS_ResourceSetList.present == NR_CSI_ResourceConfig__csi_RS_ResourceSetList_PR_nzp_CSI_RS_SSB &&
         csires->csi_RS_ResourceSetList.choice.nzp_CSI_RS_SSB->nzp_CSI_RS_ResourceSetList) {
        nzp = csires->csi_RS_ResourceSetList.choice.nzp_CSI_RS_SSB->nzp_CSI_RS_ResourceSetList->list.array[0];
      }
    }

    if (csi_measconfig->nzp_CSI_RS_ResourceToAddModList != NULL && nzp != NULL) {

      NR_NZP_CSI_RS_Resource_t *nzpcsi;
      int period, offset;

      nfapi_nr_dl_tti_request_body_t *dl_req = &DL_req->dl_tti_request_body;

      for (int id = 0; id < csi_measconfig->nzp_CSI_RS_ResourceToAddModList->list.count; id++){
        nzpcsi = csi_measconfig->nzp_CSI_RS_ResourceToAddModList->list.array[id];
        // transmitting CSI-RS only for current BWP
        if (nzpcsi->nzp_CSI_RS_ResourceId != *nzp)
          continue;

        NR_CSI_RS_ResourceMapping_t  resourceMapping = nzpcsi->resourceMapping;
        csi_period_offset(NULL, nzpcsi->periodicityAndOffset, &period, &offset);

        if((frame * n_slots_frame + slot - offset) % period == 0) {

          LOG_D(NR_MAC,"Scheduling CSI-RS in frame %d slot %d Resource ID %ld\n", frame, slot, nzpcsi->nzp_CSI_RS_ResourceId);
          NR_beam_alloc_t beam_csi = beam_allocation_procedure(&gNB_mac->beam_info, frame, slot, UE->UE_beam_index, n_slots_frame);
          AssertFatal(beam_csi.idx >= 0, "Cannot allocate CSI-RS in any available beam\n");
          uint16_t *vrb_map = gNB_mac->common_channels[CC_id].vrb_map[beam_csi.idx];
          UE_info->sched_csirs |= (1 << dl_bwp->bwp_id);

          nfapi_nr_dl_tti_request_pdu_t *dl_tti_csirs_pdu = &dl_req->dl_tti_pdu_list[dl_req->nPDUs];
          memset((void*)dl_tti_csirs_pdu,0,sizeof(nfapi_nr_dl_tti_request_pdu_t));
          dl_tti_csirs_pdu->PDUType = NFAPI_NR_DL_TTI_CSI_RS_PDU_TYPE;
          dl_tti_csirs_pdu->PDUSize = (uint8_t)(2+sizeof(nfapi_nr_dl_tti_csi_rs_pdu));

          nfapi_nr_dl_tti_csi_rs_pdu_rel15_t *csirs_pdu_rel15 = &dl_tti_csirs_pdu->csi_rs_pdu.csi_rs_pdu_rel15;
          csirs_pdu_rel15->precodingAndBeamforming.num_prgs = 1;
          csirs_pdu_rel15->precodingAndBeamforming.prg_size = resourceMapping.freqBand.nrofRBs; //1 PRG of max size
          csirs_pdu_rel15->precodingAndBeamforming.dig_bf_interfaces = 1;
          csirs_pdu_rel15->precodingAndBeamforming.prgs_list[0].pm_idx = 0;
          csirs_pdu_rel15->precodingAndBeamforming.prgs_list[0].dig_bf_interface_list[0].beam_idx = UE->UE_beam_index;
          csirs_pdu_rel15->bwp_size = dl_bwp->BWPSize;
          csirs_pdu_rel15->bwp_start = dl_bwp->BWPStart;
          csirs_pdu_rel15->subcarrier_spacing = dl_bwp->scs;
          if (dl_bwp->cyclicprefix)
            csirs_pdu_rel15->cyclic_prefix = *dl_bwp->cyclicprefix;
          else
            csirs_pdu_rel15->cyclic_prefix = 0;

          // According to last paragraph of TS 38.214 5.2.2.3.1
          if (resourceMapping.freqBand.startingRB < dl_bwp->BWPStart) {
            csirs_pdu_rel15->start_rb = dl_bwp->BWPStart;
          } else {
            csirs_pdu_rel15->start_rb = resourceMapping.freqBand.startingRB;
          }
          if (resourceMapping.freqBand.nrofRBs > (dl_bwp->BWPStart + dl_bwp->BWPSize - csirs_pdu_rel15->start_rb)) {
            csirs_pdu_rel15->nr_of_rbs = dl_bwp->BWPStart + dl_bwp->BWPSize - csirs_pdu_rel15->start_rb;
          } else {
            csirs_pdu_rel15->nr_of_rbs = resourceMapping.freqBand.nrofRBs;
          }
          AssertFatal(csirs_pdu_rel15->nr_of_rbs >= 24, "CSI-RS has %d RBs, but the minimum is 24\n", csirs_pdu_rel15->nr_of_rbs);

          csirs_pdu_rel15->csi_type = 1; // NZP-CSI-RS
          csirs_pdu_rel15->symb_l0 = resourceMapping.firstOFDMSymbolInTimeDomain;
          if (resourceMapping.firstOFDMSymbolInTimeDomain2)
            csirs_pdu_rel15->symb_l1 = *resourceMapping.firstOFDMSymbolInTimeDomain2;
          csirs_pdu_rel15->cdm_type = resourceMapping.cdm_Type;
          csirs_pdu_rel15->freq_density = resourceMapping.density.present;
          if ((resourceMapping.density.present == NR_CSI_RS_ResourceMapping__density_PR_dot5)
              && (resourceMapping.density.choice.dot5 == NR_CSI_RS_ResourceMapping__density__dot5_evenPRBs))
            csirs_pdu_rel15->freq_density--;
          csirs_pdu_rel15->scramb_id = nzpcsi->scramblingID;
          csirs_pdu_rel15->power_control_offset = nzpcsi->powerControlOffset + 8;
          if (nzpcsi->powerControlOffsetSS)
            csirs_pdu_rel15->power_control_offset_ss = *nzpcsi->powerControlOffsetSS;
          else
            csirs_pdu_rel15->power_control_offset_ss = 1; // 0 dB
          switch(resourceMapping.frequencyDomainAllocation.present){
            case NR_CSI_RS_ResourceMapping__frequencyDomainAllocation_PR_row1:
              csirs_pdu_rel15->row = 1;
              csirs_pdu_rel15->freq_domain = ((resourceMapping.frequencyDomainAllocation.choice.row1.buf[0])>>4)&0x0f;
              for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++)
                vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 1);
              break;
            case NR_CSI_RS_ResourceMapping__frequencyDomainAllocation_PR_row2:
              csirs_pdu_rel15->row = 2;
              csirs_pdu_rel15->freq_domain = (((resourceMapping.frequencyDomainAllocation.choice.row2.buf[1]>>4)&0x0f) |
                                             ((resourceMapping.frequencyDomainAllocation.choice.row2.buf[0]<<4)&0xff0));
              for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++)
                vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 1);
              break;
            case NR_CSI_RS_ResourceMapping__frequencyDomainAllocation_PR_row4:
              csirs_pdu_rel15->row = 4;
              csirs_pdu_rel15->freq_domain = ((resourceMapping.frequencyDomainAllocation.choice.row4.buf[0])>>5)&0x07;
              for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++)
                vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 1);
              break;
            case NR_CSI_RS_ResourceMapping__frequencyDomainAllocation_PR_other:
              csirs_pdu_rel15->freq_domain = ((resourceMapping.frequencyDomainAllocation.choice.other.buf[0])>>2)&0x3f;
              // determining the row of table 7.4.1.5.3-1 in 38.211
              switch(resourceMapping.nrofPorts){
                case NR_CSI_RS_ResourceMapping__nrofPorts_p1:
                  AssertFatal(1==0,"Resource with 1 CSI port shouldn't be within other rows\n");
                  break;
                case NR_CSI_RS_ResourceMapping__nrofPorts_p2:
                  csirs_pdu_rel15->row = 3;
                  for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++)
                    vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 1);
                  break;
                case NR_CSI_RS_ResourceMapping__nrofPorts_p4:
                  csirs_pdu_rel15->row = 5;
                  for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++)
                    vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 2);
                  break;
                case NR_CSI_RS_ResourceMapping__nrofPorts_p8:
                  if (resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_cdm4_FD2_TD2) {
                    csirs_pdu_rel15->row = 8;
                    for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++)
                      vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 2);
                  }
                  else{
                    int num_k = 0;
                    for (int k=0; k<6; k++)
                      num_k+=(((csirs_pdu_rel15->freq_domain)>>k)&0x01);
                    if(num_k==4) {
                      csirs_pdu_rel15->row = 6;
                      for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++)
                        vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 1);
                    }
                    else {
                      csirs_pdu_rel15->row = 7;
                      for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++)
                        vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 2);
                    }
                  }
                  break;
                case NR_CSI_RS_ResourceMapping__nrofPorts_p12:
                  if (resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_cdm4_FD2_TD2) {
                    csirs_pdu_rel15->row = 10;
                    for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++)
                      vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 2);
                  }
                  else {
                    csirs_pdu_rel15->row = 9;
                    for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++)
                      vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 1);
                  }
                  break;
                case NR_CSI_RS_ResourceMapping__nrofPorts_p16:
                  if (resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_cdm4_FD2_TD2)
                    csirs_pdu_rel15->row = 12;
                  else
                    csirs_pdu_rel15->row = 11;
                  for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++)
                    vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 2);
                  break;
                case NR_CSI_RS_ResourceMapping__nrofPorts_p24:
                  if (resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_cdm4_FD2_TD2) {
                    csirs_pdu_rel15->row = 14;
                    for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++)
                      vrb_map[rb] |= (SL_to_bitmap(csirs_pdu_rel15->symb_l0, 2) | SL_to_bitmap(csirs_pdu_rel15->symb_l1, 2));
                  }
                  else{
                    if (resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_cdm8_FD2_TD4) {
                      csirs_pdu_rel15->row = 15;
                      for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++)
                        vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 3);
                    }
                    else {
                      csirs_pdu_rel15->row = 13;
                      for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++)
                        vrb_map[rb] |= (SL_to_bitmap(csirs_pdu_rel15->symb_l0, 2) | SL_to_bitmap(csirs_pdu_rel15->symb_l1, 2));
                    }
                  }
                  break;
                case NR_CSI_RS_ResourceMapping__nrofPorts_p32:
                  if (resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_cdm4_FD2_TD2) {
                    csirs_pdu_rel15->row = 17;
                    for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++)
                      vrb_map[rb] |= (SL_to_bitmap(csirs_pdu_rel15->symb_l0, 2) | SL_to_bitmap(csirs_pdu_rel15->symb_l1, 2));
                  }
                  else{
                    if (resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_cdm8_FD2_TD4) {
                      csirs_pdu_rel15->row = 18;
                      for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++)
                        vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 3);
                    }
                    else {
                      csirs_pdu_rel15->row = 16;
                      for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++)
                        vrb_map[rb] |= (SL_to_bitmap(csirs_pdu_rel15->symb_l0, 2) | SL_to_bitmap(csirs_pdu_rel15->symb_l1, 2));
                    }
                  }
                  break;
              default:
                AssertFatal(1==0,"Invalid number of ports in CSI-RS resource\n");
              }
              break;
          default:
            AssertFatal(1==0,"Invalid freqency domain allocation in CSI-RS resource\n");
          }
          dl_req->nPDUs++;
        }
      }
    }
  }
}

void nr_measgap_scheduling(gNB_MAC_INST *nr_mac, frame_t frame, sub_frame_t slot)
{
  NR_SCHED_ENSURE_LOCKED(&nr_mac->sched_lock);

  NR_UEs_t *UE_info = &nr_mac->UE_info;
  UE_iterator(UE_info->connected_ue_list, UE) {
    measgap_config_t *mgc = &UE->measgap_config;
    if (!mgc->enable)
      continue;

    const int slots_frame = nr_mac->frame_structure.numb_slots_frame;
    const frame_t f = (frame + (slot + mgc->n_slots_advance) / slots_frame) % 1024;
    const slot_t s = (slot + mgc->n_slots_advance) % slots_frame;

    // TS 38 331 - Section 5.5.2.9 Measurement gap configuration
    if (!(((f % (mgc->mgrp_ms / 10)) == (mgc->gapOffset / 10)) && (s == mgc->gapOffset % 10)))
      continue;

    NR_timer_t *t = &UE->UE_sched_ctrl.transm_interrupt;
    interrupt_followup_action_t a = nr_timer_is_active(t) ? UE->interrupt_action : FOLLOW_INSYNC;
    // start a timer to stop scheduling UE during MeasGap, or extend timer for
    // duration of measGap with existing follow-up action
    // TODO: if the timer is running, it might be to stop scheduling of the UE afterwards, but then it does not make sense to stop
    //  scheduling the UE now, we should maybe just skip it?
    if (!nr_timer_is_active(t) || nr_timer_remaining_time(t) < mgc->mgl_slots) {
      nr_mac_trigger_ul_failure(&UE->UE_sched_ctrl, UE->current_DL_BWP.scs); /* set the UE to "not active" */
      nr_mac_interrupt_ue_transmission(nr_mac, UE, a, mgc->mgl_slots);
    }
  }
}

void nr_mac_clean_cellgroup(NR_CellGroupConfig_t *cell_group)
{
  DevAssert(cell_group != NULL);
  /* remove a reconfigurationWithSync, we don't need it anymore */
  if (cell_group->spCellConfig && cell_group->spCellConfig->reconfigurationWithSync != NULL) {
    ASN_STRUCT_FREE(asn_DEF_NR_ReconfigurationWithSync, cell_group->spCellConfig->reconfigurationWithSync);
    cell_group->spCellConfig->reconfigurationWithSync = NULL;
  }
  /* remove the rlc_BearerToReleaseList, we don't need it anymore */
  if (cell_group->rlc_BearerToReleaseList != NULL) {
    struct NR_CellGroupConfig__rlc_BearerToReleaseList *l = cell_group->rlc_BearerToReleaseList;
    asn_sequence_del(&l->list, l->list.count, /* free */ 1);
    free(cell_group->rlc_BearerToReleaseList);
    cell_group->rlc_BearerToReleaseList = NULL;
  }
  /* remove reestablishRLC, we don't need it anymore */
  for (int i = 0; i < cell_group->rlc_BearerToAddModList->list.count; ++i)
    free_and_zero(cell_group->rlc_BearerToAddModList->list.array[i]->reestablishRLC);
}

int nr_mac_get_reconfig_delay_slots(NR_SubcarrierSpacing_t scs)
{
  /* we previously assumed a specific "slot ahead" value for the PHY processing
   * time. However, we cannot always know it (e.g., third-party PHY), so simply
   * assume a tentative worst-case slot processing time */
  const int sl_ahead = 10;
  /* 16ms seems to be the most common: See 38.331 Tab 12.1-1 */
  int delay_ms = NR_RRC_RECONFIGURATION_DELAY_MS + NR_RRC_BWP_SWITCHING_DELAY_MS;
  return (delay_ms << scs) + sl_ahead;
}

int nr_mac_interrupt_ue_transmission(gNB_MAC_INST *mac, NR_UE_info_t *UE, interrupt_followup_action_t action, int slots)
{
  DevAssert(mac != NULL);
  DevAssert(UE != NULL);
  NR_SCHED_ENSURE_LOCKED(&mac->sched_lock);

  nr_timer_setup(&UE->UE_sched_ctrl.transm_interrupt, slots, 1);
  nr_timer_start(&UE->UE_sched_ctrl.transm_interrupt);
  UE->interrupt_action = action;

  // it might happen that timing advance command should be sent during the UE
  // inactivity time. To prevent this, set a variable as if we would have just
  // sent it. This way, another TA command will for sure be sent in some
  // frames, after the inactivity of the UE.
  UE->UE_sched_ctrl.ta_frame = (mac->frame - 1 + 1024) % 1024;

  LOG_D(NR_MAC, "UE %04x: Interrupt UE transmission (%d slots) action %d\n", UE->rnti, slots, UE->interrupt_action);
  return 0;
}

int nr_transmission_action_indicator_stop(gNB_MAC_INST *mac, NR_UE_info_t *UE_info)
{
  int delay = nr_mac_get_reconfig_delay_slots(UE_info->current_DL_BWP.scs);
  nr_mac_interrupt_ue_transmission(mac, UE_info, FOLLOW_OUTOFSYNC, delay);
  LOG_I(NR_MAC, "gNB-DU received the TransmissionActionIndicator with Stop value for UE %04x\n", UE_info->rnti);
  return 0;
}

void nr_mac_trigger_release_complete(gNB_MAC_INST *mac, int rnti)
{
  // the CU might not know such UE, e.g., because we never sent a message to
  // it, so there might not be a corresponding entry for such UE in the look up
  // table. This can happen, e.g., on Msg.3 with C-RNTI, where we create a UE
  // MAC context, decode the PDU, find the C-RNTI MAC CE, and then throw the
  // newly created context away. See also in _nr_rx_sdu() and commit 93f59a3c6e56f
  if (!du_exists_f1_ue_data(rnti)) 
    return;

  // unlock the scheduler temporarily to prevent possible deadlocks with
  // du_remove_f1_ue_data() (and also while sending the message to RRC)
  NR_SCHED_UNLOCK(&mac->sched_lock);
  f1_ue_data_t ue_data = du_get_f1_ue_data(rnti);
  f1ap_ue_context_rel_cplt_t complete = {
    .gNB_CU_ue_id = ue_data.secondary_ue,
    .gNB_DU_ue_id = rnti,
  };
  mac->mac_rrc.ue_context_release_complete(&complete);

  du_remove_f1_ue_data(rnti);
  NR_SCHED_LOCK(&mac->sched_lock);
}

void nr_mac_release_ue(gNB_MAC_INST *mac, int rnti)
{
  NR_SCHED_ENSURE_LOCKED(&mac->sched_lock);

  nr_rlc_remove_ue(rnti);
  mac_remove_nr_ue(mac, rnti);
}

void nr_mac_update_timers(module_id_t module_id, frame_t frame, slot_t slot)
{
  gNB_MAC_INST *mac = RC.nrmac[module_id];

  /* already mutex protected: held in gNB_dlsch_ulsch_scheduler() */
  NR_SCHED_ENSURE_LOCKED(&mac->sched_lock);

  NR_UEs_t *UE_info = &mac->UE_info;
  UE_iterator(UE_info->connected_ue_list, UE) {
    NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;

    if (nr_mac_check_release(sched_ctrl, UE->rnti)) {
      // trigger release first as nr_mac_release_ue() invalidates UE ptr
      nr_mac_trigger_release_complete(mac, UE->rnti);
      nr_mac_release_ue(mac, UE->rnti);
      // go back to examine the next UE, which is at the position the
      // current UE was
      UE--;
      continue;
    }

    /* check if UL failure and trigger release request if necessary */
    bool released = nr_mac_check_ul_failure(mac, UE->rnti, sched_ctrl);
    if (released) {
      // go back to examine the next UE, which is at the position the current UE was
      UE--;
      continue;
    }

    if (nr_timer_tick(&sched_ctrl->transm_interrupt)) {
      /* expired */
      nr_timer_stop(&sched_ctrl->transm_interrupt);
      if (UE->interrupt_action == FOLLOW_OUTOFSYNC) {
        nr_mac_trigger_ul_failure(sched_ctrl, UE->current_DL_BWP.scs);
      } else {
        DevAssert(UE->interrupt_action == FOLLOW_INSYNC);
        nr_mac_reset_ul_failure(sched_ctrl);
      }
    }
  }
}

int ul_buffer_index(int frame, int slot, int slots_per_frame, int size)
{
  const int abs_slot = frame * slots_per_frame + slot;
  return abs_slot % size;
}

void UL_tti_req_ahead_initialization(gNB_MAC_INST *gNB, int n, int CCid, frame_t frameP, int slotP)
{

  if(gNB->UL_tti_req_ahead[CCid][1].Slot == 1)
    return;

  /* fill in slot/frame numbers: slot is fixed, frame will be updated by scheduler
   * consider that scheduler runs sl_ahead: the first sl_ahead slots are
   * already "in the past" and thus we put frame 1 instead of 0! */
  for (int i = 0; i < gNB->UL_tti_req_ahead_size; ++i) {
    int abs_slot = frameP * n + slotP + i;
    nfapi_nr_ul_tti_request_t *req = &gNB->UL_tti_req_ahead[CCid][abs_slot % gNB->UL_tti_req_ahead_size];
    req->SFN = (abs_slot / n) % MAX_FRAME_NUMBER;
    req->Slot = abs_slot % n;
  }
}

int get_fapi_beamforming_index(gNB_MAC_INST *mac, int ssb_idx)
{
  int beam_idx = mac->fapi_beam_index[ssb_idx];
  AssertFatal(beam_idx >= 0, "Invalid beamforming index %d\n", beam_idx);
  return beam_idx;
}

// TODO this is a placeholder for a possibly more complex function
// for now the fapi beam index is the number of SSBs transmitted before ssb_index i
void fapi_beam_index_allocation(NR_ServingCellConfigCommon_t *scc, const nr_mac_config_t *config, gNB_MAC_INST *mac)
{
  if (mac->beam_info.beam_mode == NO_BEAM_MODE)
    return;
  int len = 0;
  uint8_t* buf = NULL;
  switch (scc->ssb_PositionsInBurst->present) {
    case NR_ServingCellConfigCommon__ssb_PositionsInBurst_PR_shortBitmap:
      len = 4;
      buf = scc->ssb_PositionsInBurst->choice.shortBitmap.buf;
      break;
    case NR_ServingCellConfigCommon__ssb_PositionsInBurst_PR_mediumBitmap:
      len = 8;
      buf = scc->ssb_PositionsInBurst->choice.mediumBitmap.buf;
      break;
    case NR_ServingCellConfigCommon__ssb_PositionsInBurst_PR_longBitmap:
      len = 64;
      buf = scc->ssb_PositionsInBurst->choice.longBitmap.buf;
      break;
    default :
      AssertFatal(false, "Invalid configuration\n");
  }
  int index = 0;
  for (int i = 0; i < len; ++i) {
    if ((buf[i / 8] >> (7 - i % 8)) & 0x1) {
      int fapi_index = mac->beam_info.beam_mode == LOPHY_BEAM_IDX ? config->bw_list[index] : index;
      mac->fapi_beam_index[i] = fapi_index;
      index++;
    } else
      mac->fapi_beam_index[i] = -1;
  }
}

static inline int get_beam_index(const NR_beam_info_t *beam_info, int frame, int slot, int slots_per_frame)
{
  return ((frame * slots_per_frame + slot) / beam_info->beam_duration) % beam_info->beam_allocation_size;
}

NR_beam_alloc_t beam_allocation_procedure(NR_beam_info_t *beam_info, int frame, int slot, int beam_index, int slots_per_frame)
{
  // if no beam allocation for analog beamforming we always return beam index 0 (no multiple beams)
  if (beam_info->beam_mode == NO_BEAM_MODE)
    return (NR_beam_alloc_t) {.new_beam = false, .idx = 0};

  const int index = get_beam_index(beam_info, frame, slot, slots_per_frame);
  for (int i = 0; i < beam_info->beams_per_period; i++) {
    NR_beam_alloc_t beam_struct = {.new_beam = false, .idx = i};
    int *beam = &beam_info->beam_allocation[i][index];
    if (*beam == -1) {
      beam_struct.new_beam = true;
      *beam = beam_index;
    }
    if (*beam == beam_index) {
      LOG_D(NR_MAC, "%d.%d Using beam structure with index %d for beam %d (%s)\n", frame, slot, beam_struct.idx, beam_index, beam_struct.new_beam ? "new beam" : "old beam");
      return beam_struct;
    }
  }

  return (NR_beam_alloc_t) {.new_beam = false, .idx = -1};
}

void reset_beam_status(NR_beam_info_t *beam_info, int frame, int slot, int beam_index, int slots_per_frame, bool new_beam)
{
  if(!new_beam) // need to reset only if the beam was allocated specifically for this instance
    return;
  const int index = get_beam_index(beam_info, frame, slot, slots_per_frame);
  for (int i = 0; i < beam_info->beams_per_period; i++) {
    if (beam_info->beam_allocation[i][index] == beam_index)
      beam_info->beam_allocation[i][index] = -1;
  }
}

void beam_selection_procedures(gNB_MAC_INST *mac, NR_UE_info_t *UE)
{
  // do not perform beam procedures if there is no beam information
  if (mac->beam_info.beam_mode == NO_BEAM_MODE)
    return;
  RSRP_report_t *rsrp_report = &UE->UE_sched_ctrl.CSI_report.ssb_rsrp_report;
  // simple beam switching algorithm -> we select beam with highest RSRP from CSI report
  int new_bf_index = get_fapi_beamforming_index(mac, rsrp_report->resource_id[0]);
  if (UE->UE_beam_index == new_bf_index)
    return; // no beam change needed

  LOG_I(NR_MAC, "[UE %x] Switching to beam with ID %d (SSB number %d)\n", UE->rnti, new_bf_index, rsrp_report->resource_id[0]);
  UE->UE_beam_index = new_bf_index;
}

void send_initial_ul_rrc_message(int rnti, const uint8_t *sdu, sdu_size_t sdu_len, void *data)
{
  gNB_MAC_INST *mac = RC.nrmac[0];
  NR_UE_info_t *UE = (NR_UE_info_t *)data;
  NR_SCHED_ENSURE_LOCKED(&mac->sched_lock);

  uint8_t du2cu[1024];
  int encoded_len = 0;
  if (UE->CellGroup)
    encoded_len = encode_cellGroupConfig(UE->CellGroup, du2cu, sizeof(du2cu));

  DevAssert(mac->f1_config.setup_req != NULL);
  AssertFatal(mac->f1_config.setup_req->num_cells_available == 1, "can handle only one cell\n");
  const f1ap_initial_ul_rrc_message_t ul_rrc_msg = {
    .plmn = mac->f1_config.setup_req->cell[0].info.plmn,
    .nr_cellid = mac->f1_config.setup_req->cell[0].info.nr_cellid,
    .gNB_DU_ue_id = rnti,
    .crnti = rnti,
    .rrc_container = (uint8_t *) sdu,
    .rrc_container_length = sdu_len,
    .du2cu_rrc_container = encoded_len ? (uint8_t *) du2cu : NULL,
    .du2cu_rrc_container_length = encoded_len
  };
  mac->mac_rrc.initial_ul_rrc_message_transfer(0, &ul_rrc_msg);
}

bool prepare_initial_ul_rrc_message(gNB_MAC_INST *mac, NR_UE_info_t *UE)
{
  NR_SCHED_ENSURE_LOCKED(&mac->sched_lock);

  /* activate SRB0 */
  if (!nr_rlc_activate_srb0(UE->rnti, UE, send_initial_ul_rrc_message))
    return false;

  if (UE->uid >= MAX_MOBILES_PER_GNB) {
    // verifying if any UE left in the meantime and it is possible to get a valid UID
    uid_linear_allocator_free(&mac->UE_info.uid_allocator, UE->uid);
    UE->uid = uid_linear_allocator_new(&mac->UE_info.uid_allocator);
    if (UE->uid >= MAX_MOBILES_PER_GNB) {
      // if the UE list is full, we should reject the UE with an RRC reject message
      // to signal the upper layers to send a reject we send an empty CellGroup information element
      UE->CellGroup = NULL;
      LOG_W(NR_MAC, "Maximum number of UE alllowed at DU reached. Cannot serve UE %04x", UE->rnti);
      return true;
    }
  }

  /* create this UE's initial CellGroup */
  int CC_id = 0;
  int srb_id = 1;
  const NR_ServingCellConfigCommon_t *scc = mac->common_channels[CC_id].ServingCellConfigCommon;
  const NR_ServingCellConfig_t *sccd = UE->is_redcap ? NULL : mac->common_channels[CC_id].pre_ServingCellConfig;
  NR_CellGroupConfig_t *cellGroupConfig = get_initial_cellGroupConfig(UE->uid, scc, sccd, &mac->radio_config, &mac->rlc_config);
  ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->CellGroup);
  UE->CellGroup = cellGroupConfig;

  if (!cellGroupConfig)
    return true;

  /* the cellGroup sent to CU specifies there is SRB1, so create it */
  DevAssert(cellGroupConfig->rlc_BearerToAddModList->list.count == 1);
  const NR_RLC_BearerConfig_t *bearer = cellGroupConfig->rlc_BearerToAddModList->list.array[0];
  DevAssert(bearer->servedRadioBearer->choice.srb_Identity == srb_id);
  nr_rlc_add_srb(UE->rnti, bearer->servedRadioBearer->choice.srb_Identity, bearer);
  int priority = bearer->mac_LogicalChannelConfig->ul_SpecificParameters->priority;
  nr_lc_config_t c = {.lcid = bearer->logicalChannelIdentity, .priority = priority};
  nr_mac_add_lcid(&UE->UE_sched_ctrl, &c);
  return true;
}

void nr_mac_trigger_release_timer(NR_UE_sched_ctrl_t *sched_ctrl, NR_SubcarrierSpacing_t subcarrier_spacing)
{
  // trigger 60ms
  sched_ctrl->release_timer = 100 << subcarrier_spacing;
}

bool nr_mac_check_release(NR_UE_sched_ctrl_t *sched_ctrl, int rnti)
{
  if (sched_ctrl->release_timer == 0)
    return false;
  sched_ctrl->release_timer--;
  return sched_ctrl->release_timer == 0;
}

#define UL_FAILURE_REQ_GRACE 10000
#define UL_FAILURE_TIMEOUT 30000
void nr_mac_trigger_ul_failure(NR_UE_sched_ctrl_t *sched_ctrl, NR_SubcarrierSpacing_t subcarrier_spacing)
{
  if (sched_ctrl->ul_failure) {
    /* already running */
    return;
  }
  sched_ctrl->ul_failure = true;
  // UL_FAILURE_TIMEOUT ms till triggering release request
  // UL_FAILURE_REQ_GRACE ms till automatically released after request
  sched_ctrl->ul_failure_timer = (UL_FAILURE_REQ_GRACE + UL_FAILURE_TIMEOUT) << subcarrier_spacing;
}

void nr_mac_reset_ul_failure(NR_UE_sched_ctrl_t *sched_ctrl)
{
  sched_ctrl->ul_failure = false;
  sched_ctrl->ul_failure_timer = 0;
  sched_ctrl->pusch_consecutive_dtx_cnt = 0;
}

/* \brief trigger a release request towards the CU.
 * \returns true if release was requested, else false (in which case the CU
 * does not know the UE, and we can safely remove it). */
bool nr_mac_request_release_ue(const gNB_MAC_INST *nrmac, int rnti)
{
  if (!du_exists_f1_ue_data(rnti)) {
    LOG_W(NR_MAC, "UE %04x: no CU-UE ID stored, cannot request release\n", rnti);
    return false;
  }
  f1_ue_data_t ue_data = du_get_f1_ue_data(rnti);
  f1ap_ue_context_rel_req_t request = {
    .gNB_CU_ue_id = ue_data.secondary_ue,
    .gNB_DU_ue_id = rnti,
    .cause = F1AP_CAUSE_RADIO_NETWORK,
    .cause_value = F1AP_CauseRadioNetwork_rl_failure_rlc,
  };
  nrmac->mac_rrc.ue_context_release_request(&request);
  return true;
}

bool nr_mac_check_ul_failure(gNB_MAC_INST *nrmac, int rnti, NR_UE_sched_ctrl_t *sched_ctrl)
{
  if (!sched_ctrl->ul_failure)
    return false;
  if (sched_ctrl->ul_failure_timer > 0)
    sched_ctrl->ul_failure_timer--;
  /* trigger once: trigger when ul_failure_timer == UL_FAILURE_REQ_GRACE( we
   * wait for a UE release command from upper layers), and UE is automatically
   * released when no answer after UL_FAILURE_REQ_GRACE */
  if (sched_ctrl->ul_failure_timer == UL_FAILURE_REQ_GRACE) {
    LOG_W(MAC, "UE %04x: request release after UL failure timer expiry\n", rnti);
    bool requested = nr_mac_request_release_ue(nrmac, rnti);
    if (!requested)
      sched_ctrl->ul_failure_timer = 0; /* force expiry */
  }
  if (sched_ctrl->ul_failure_timer == 0) {
    LOG_W(NR_MAC, "UE %04x: no answer for release request, dropping UE\n", rnti);
    nr_mac_release_ue(nrmac, rnti);
    return true;
  }
  return false;
}

void nr_mac_trigger_reconfiguration(const gNB_MAC_INST *nrmac, const NR_UE_info_t *UE)
{
  DevAssert(UE->CellGroup != NULL);
  uint8_t buf[2048];
  asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_CellGroupConfig, NULL, UE->CellGroup, buf, sizeof(buf));
  AssertFatal(enc_rval.encoded > 0, "ASN1 encoding of CellGroupConfig failed, failed type %s\n", enc_rval.failed_type->name);
  du_to_cu_rrc_information_t du2cu = {
    .cellGroupConfig = buf,
    .cellGroupConfig_length = (enc_rval.encoded + 7) >> 3,
  };
  f1_ue_data_t ue_data = du_get_f1_ue_data(UE->rnti);
  f1ap_ue_context_modif_required_t required = {
    .gNB_CU_ue_id = ue_data.secondary_ue,
    .gNB_DU_ue_id = UE->rnti,
    .du_to_cu_rrc_information = &du2cu,
    .cause = F1AP_CAUSE_RADIO_NETWORK,
    .cause_value = F1AP_CauseRadioNetwork_action_desirable_for_radio_reasons,
  };
  nrmac->mac_rrc.ue_context_modification_required(&required);
}

/* \brief add bearers from CellGroupConfig.
 *
 * This is a kind of hack, as this should be processed through a F1 UE Context
 * setup request, but some modes do not use that (NSA/do-ra/phy_test).  */
void process_addmod_bearers_cellGroupConfig(NR_UE_sched_ctrl_t *sched_ctrl, const struct NR_CellGroupConfig__rlc_BearerToAddModList *addmod)
{
  if (addmod == NULL)
    return; /* nothing to do */

  for (int i = 0; i < addmod->list.count; ++i) {
    const NR_RLC_BearerConfig_t *conf = addmod->list.array[i];
    int lcid = conf->logicalChannelIdentity;
    nr_lc_config_t c = {.lcid = lcid};
    nr_mac_add_lcid(sched_ctrl, &c);
  }
}

long get_lcid_from_drbid(int drb_id)
{
  return drb_id + 3; /* LCID is DRB + 3 */
}

long get_lcid_from_srbid(int srb_id)
{
  return srb_id;
}

static bool eq_lcid_config(const void *vval, const void *vit)
{
  const nr_lc_config_t *val = (const nr_lc_config_t *)vval;
  const nr_lc_config_t *it = (const nr_lc_config_t *)vit;
  return it->lcid == val->lcid;
}

static int cmp_lc_config(const void *va, const void *vb)
{
  const nr_lc_config_t *a = (const nr_lc_config_t *)va;
  const nr_lc_config_t *b = (const nr_lc_config_t *)vb;

  if (a->priority < b->priority)
    return -1;
  if (a->priority == b->priority)
    return 0;
  return 1;
}

nr_lc_config_t *nr_mac_get_lc_config(NR_UE_sched_ctrl_t* sched_ctrl, int lcid)
{
  nr_lc_config_t c = {.lcid = lcid};
  elm_arr_t elm = find_if(&sched_ctrl->lc_config, &c, eq_lcid_config);
  if (elm.found)
    return elm.it;
  else
    return NULL;
}

bool nr_mac_add_lcid(NR_UE_sched_ctrl_t* sched_ctrl, const nr_lc_config_t *c)
{
  elm_arr_t elm = find_if(&sched_ctrl->lc_config, (void *) c, eq_lcid_config);
  if (elm.found) {
    LOG_I(NR_MAC, "cannot add LCID %d: already present, updating configuration\n", c->lcid);
    nr_lc_config_t *exist = (nr_lc_config_t *)elm.it;
    *exist = *c;
  } else {
    LOG_D(NR_MAC, "Add LCID %d\n", c->lcid);
    seq_arr_push_back(&sched_ctrl->lc_config, (void*) c, sizeof(*c));
  }
  void *base = seq_arr_front(&sched_ctrl->lc_config);
  size_t nmemb = seq_arr_size(&sched_ctrl->lc_config);
  size_t size = sizeof(*c);
  qsort(base, nmemb, size, cmp_lc_config);
  return true;
}

bool nr_mac_remove_lcid(NR_UE_sched_ctrl_t *sched_ctrl, long lcid)
{
  nr_lc_config_t c = {.lcid = lcid};
  elm_arr_t elm = find_if(&sched_ctrl->lc_config, &c, eq_lcid_config);
  if (!elm.found) {
    LOG_E(NR_MAC, "can not remove LC: no such LC with ID %ld\n", lcid);
    return false;
  }

  seq_arr_erase(&sched_ctrl->lc_config, elm.it);
  return true;
}

bool nr_mac_get_new_rnti(NR_UEs_t *UEs, rnti_t *rnti)
{
  int loop = 0;
  bool exist_connected_ue, exist_in_pending_ra_ue;
  do {
    *rnti = (taus() % 0xffef) + 1;
    exist_connected_ue = find_nr_UE(UEs, *rnti) != NULL;
    exist_in_pending_ra_ue = find_ra_UE(UEs, *rnti) != NULL;
    loop++;
  } while (loop < 100 && (exist_connected_ue || exist_in_pending_ra_ue));
  return loop < 100; // nothing found: loop count 100
}

/// @brief Orders PDCCH aggregation levels so that we first check desired aggregation level according to
///        pdcch_cl_adjust
/// @param agg_level_search_order in/out 5-element array of aggregation levels from 0 to 4
/// @param pdcch_cl_adjust value from 0 to 1 indication channel impariments (0 - good channel, 1 - bad channel)
static void determine_aggregation_level_search_order(int agg_level_search_order[NUM_PDCCH_AGG_LEVELS], float pdcch_cl_adjust)
{
  int desired_agg_level_index = round(4 * pdcch_cl_adjust);
  int agg_level_search_index = 0;
  for (int i = desired_agg_level_index; i < NUM_PDCCH_AGG_LEVELS; i++) {
    agg_level_search_order[agg_level_search_index++] = i;
  }
  for (int i = desired_agg_level_index - 1; i >= 0; i--) {
    agg_level_search_order[agg_level_search_index++] = i;
  }
}

/// @brief Update PDCCH closed loop adjust for UE depending on detection of feedback.
/// @param sched_ctrl UE scheduling control info
/// @param feedback_not_detected Whether feedback (PUSCH or HARQ) was detected
void nr_mac_update_pdcch_closed_loop_adjust(NR_UE_sched_ctrl_t *sched_ctrl, bool feedback_not_detected)
{
  if (feedback_not_detected) {
    sched_ctrl->pdcch_cl_adjust = min(1, sched_ctrl->pdcch_cl_adjust + 0.05);
  } else {
    sched_ctrl->pdcch_cl_adjust = max(0, sched_ctrl->pdcch_cl_adjust - 0.01);
  }
}
